home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / flockFlickrService.js < prev    next >
Text File  |  2007-10-12  |  95KB  |  2,850 lines

  1. // vim: ts=2 sw=2 expandtab cindent
  2. //
  3. // BEGIN FLOCK GPL
  4. // 
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. // 
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. // 
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. // 
  17. // END FLOCK GPL
  18. //
  19.  
  20. const ENABLE_DEBUG = true; // switch to turn off slow debug code for production
  21. function DEBUG(x) { if (ENABLE_DEBUG) debug("flockFlickrService: "+x+"\n"); }
  22.  
  23. const Cc = Components.classes;
  24. const Ci = Components.interfaces;
  25. const Cr = Components.results;
  26. const Cu = Components.utils;
  27.  
  28. const FLICKR_TITLE                  = "Flickr Web Service";
  29. const FLICKR_FAVICON                = "http://www.flickr.com/favicon.ico";
  30. const FLICKR_CID                    = Components.ID("{db720a5c-6315-49bf-a39f-b4d4aa5ed142}");
  31. const FLICKR_CONTRACTID             = "@flock.com/?photo-api-flickr;1";
  32. const SERVICE_ENABLED_PREF          = "flock.service.flickr.enabled";
  33. const CATEGORY_COMPONENT_NAME       = "Flickr JS Component"
  34. const CATEGORY_ENTRY_NAME           = "flickr"
  35.  
  36. const flockIError                   = Components.interfaces.flockIError;
  37. const flockIPhoto                   = Components.interfaces.flockIPhoto;
  38. const flockIPhotoAccount            = Components.interfaces.flockIPhotoAccount;
  39. const flockIPhotoAlbum              = Components.interfaces.flockIPhotoAlbum;
  40. const flockIPhotoAPI                = Components.interfaces.flockIPhotoAPI;
  41. const flockIPhotoPerson             = Components.interfaces.flockIPhotoPerson;
  42.  
  43. const FLOCK_PHOTO_CONTRACTID        = "@flock.com/photo;1";
  44. const FLOCK_PHOTOPERSON_CONTRACTID  = "@flock.com/photo-person;1";
  45. const FLOCK_PHOTO_ALBUM_CONTRACTID  = "@flock.com/photo-album;1";
  46. const FLOCK_PHOTO_ACCOUNT_CONTRACTID = "@flock.com/photo-account;1";
  47. const FLOCK_ERROR_CONTRACTID        = "@flock.com/error;1";
  48. const PHOTOAPIMGR_CONTRACTID        = "@flock.com/photo-api-manager;1?";
  49.  
  50. const RDFS = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  51.                        .getService(Components.interfaces.nsIRDFService);
  52.  
  53. var gCompTK;
  54. function getCompTK() {
  55.   if (!gCompTK) {
  56.     gCompTK = Components.classes["@flock.com/singleton;1"]
  57.                         .getService(Components.interfaces.flockISingleton)
  58.                         .getSingleton("chrome://browser/content/flock/services/common/load-compTK.js")
  59.                         .wrappedJSObject;
  60.   }
  61.   return gCompTK;
  62. }
  63.  
  64. var gTimers = [];  // For use with the scheduler
  65.  
  66. function Namespace(ns) { return function (arg) { return RDFS.GetResource (ns+arg); } }
  67. const FLRDF = Namespace("http://flock.com/rdf#");
  68.  
  69. const FLICKR_STRING_BUNDLE = "chrome://flock/locale/services/flickr/flickr.properties";
  70.  
  71. // String defaults... may be updated later through Web Detective
  72. var gStrings = {
  73.   "domains": "flickr.com,yahoo.com",
  74.   "userlogin": "http://flickr.com/signin/",
  75.   "privatemessage": "http://www.flickr.com/messages_write.gne",
  76.   "userprofile": "http://www.flickr.com/people/%accountid%/",
  77.   "editprofile": "http://www.flickr.com/profile_edit.gne",
  78.   "relationship": "http://www.flickr.com/relationship.gne?id=%accountid%",
  79.   "userphotos": "http://www.flickr.com/photos/%accountid%/",
  80.   "photopage": "http://www.flickr.com/photos/%accountid%/%photoid%/",
  81.   "commentsreceivedRSS": "http://api.flickr.com/services/feeds/activity.gne?id=%accountid%&format=rss_200",
  82.   "commentrepliesRSS": "http://api.flickr.com/services/feeds/photos_comments.gne?user_id=%accountid%&format=rss_200",
  83.   "staticimage": "http://static.flickr.com/%server%/%photoid%_%secret%%size%.jpg"
  84. };
  85.  
  86. function flickrPhoto() {
  87. }
  88.  
  89. flickrPhoto.prototype= {
  90.   id: "",
  91.   thumbnail: "",
  92.   webPageUrl: "",
  93.   midSizePhoto: "",
  94.   largeSizePhoto: "",
  95.   title: "",
  96.   username: "",
  97.   userid: "",
  98.   is_public: "true",
  99.   is_video: "false",
  100.   svcShortName: 'flickr',
  101.   buildTooltip: function( ) {
  102.     // do we have to use document from the window to ceate elements? -- ja
  103.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  104.                        .getService(Components.interfaces.nsIWindowMediator);
  105.     var win = wm.getMostRecentWindow('navigator:browser');
  106.     if (!win) return null;
  107.  
  108.     var hbox = win.document.createElement('hbox');
  109.     var img = win.document.createElement('image');
  110.     img.setAttribute('src', this.icon );
  111.     hbox.appendChild(img);
  112.     var box = win.document.createElement('vbox');
  113.     box.setAttribute('style', 'max-width: 250px');
  114.     var title = win.document.createElement('label');
  115.     title.setAttribute('value', this.title );
  116.     title.setAttribute('crop', 'end');
  117.     box.appendChild(title);
  118.     var lbl = win.document.createElement('label');
  119.     lbl.setAttribute('value', this.username );
  120.     lbl.setAttribute('class', 'user');
  121.     box.appendChild(lbl);
  122.     hbox.appendChild(box)
  123.  
  124.     var vbox = win.document.createElement('vbox');
  125.     var cbox = win.document.createElement('hbox'); 
  126.     var largeImg = win.document.createElement('image');
  127.     largeImg.setAttribute('src', this.midSizePhoto);
  128.     largeImg.setAttribute('style', 'margin-bottom: 2px;');
  129.     var spacer = win.document.createElement('spacer');
  130.     spacer.setAttribute('flex', '1');
  131.     cbox.appendChild(largeImg);
  132.     cbox.appendChild(spacer);
  133.     vbox.appendChild(cbox);
  134.     vbox.appendChild(hbox);
  135.  
  136.     return vbox;
  137.   },
  138.   buildHTML: function( ) {
  139.     return '<a title="'+this.title+'" href="'+this.webPageUrl+'"><img src="'+this.midSizePhoto+'" border="0" /></a>';
  140.   },
  141.   buildLargeHTML: function( ) {
  142.     return '<a title="'+this.title+'" href="'+this.webPageUrl+'"><img src="'+this.largeSizePhoto+'" border="0" /></a>';
  143.   },
  144.   buildBBCode: function ( ) {
  145.     return '[url=' + this.webPageUrl + '][img]'+ this.largeSizePhoto +'[/img][/url]';
  146.   },
  147.   QueryInterface: function(iid) {
  148.     if (!iid.equals(Components.interfaces.nsISupports) &&
  149.         !iid.equals(Components.interfaces.flockIPhoto)) {
  150.       throw Components.results.NS_ERROR_NO_INTERFACE;
  151.     }
  152.     return this;
  153.   }
  154. };
  155.  
  156.  
  157. // ====================================================
  158. // ========== BEGIN General helper functions ==========
  159. // ====================================================
  160.  
  161. function loadSubScript(spec)
  162. {
  163.   var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  164.               .getService(Components.interfaces.mozIJSSubScriptLoader);
  165.   var context = {};
  166.   loader.loadSubScript(spec, context);
  167.   return context;
  168. }
  169.  
  170. function loadLibraryFromSpec(aSpec)
  171. {
  172.   var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
  173.                          .getService(Components.interfaces.mozIJSSubScriptLoader);
  174.   loader.loadSubScript(aSpec);
  175. }
  176.  
  177. loadSubScript("chrome://browser/content/utilityOverlay.js");
  178.  
  179. loadLibraryFromSpec("chrome://browser/content/flock/common/flocksafe.js");
  180. loadLibraryFromSpec("chrome://browser/content/flock/common/md5.js");
  181. loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
  182.  
  183. function flock_flickrBuildPhotoUrl(server, id, secret, size)
  184. {
  185.   var convertedSize = "";
  186.   switch (size) {
  187.     case "square": convertedSize = "_s"; break;
  188.     case "thumbnail": convertedSize = "_t"; break;
  189.     case "small": convertedSize = "_m"; break;
  190.     case "medium": convertedSize = "_d"; break;
  191.     case "large": convertedSize = "_b"; break;
  192.     case "original": convertedSize = "_o"; break;
  193.     default: convertedSize = "";
  194.   }
  195.   return gStrings["staticimage"].replace("%server%", server)
  196.                                 .replace("%photoid%", id)
  197.                                 .replace("%secret%", secret)
  198.                                 .replace("%size%", convertedSize);
  199. }
  200.  
  201. function flock_flickrBuildPageUrl(aUserID, aPhotoID) {
  202.   return gStrings["photopage"].replace("%accountid%", aUserID).replace("%photoid%", aPhotoID);
  203. }
  204.  
  205. // refresh the mediabar if it is open and contains private media
  206. function flock_refreshMediabarIfHasPrivate() {
  207.   var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  208.                      .getService(Components.interfaces.nsIWindowMediator);
  209.   var win = wm.getMostRecentWindow("navigator:browser");
  210.   // if the mediabar is open
  211.   if (win &&
  212.       win.document.getElementById("mediabar") &&
  213.       win.document.getElementById("mediabar").getAttribute("hidden") != "true" &&
  214.       win.TopbarJSCtx().gPhotoDrawer &&
  215.       win.TopbarJSCtx().gPhotoDrawer.mHasPrivateMedia)
  216.   {
  217.       win.TopbarJSCtx().gPhotoDrawer.refreshView();
  218.   }
  219. }
  220.  
  221. // ===============================================
  222. // ========== BEGIN flickrService class ==========
  223. // ===============================================
  224.  
  225. function flickrService()
  226. {
  227.   this.obs = Components.classes["@mozilla.org/observer-service;1"]
  228.                        .getService(Components.interfaces.nsIObserverService);
  229.  
  230.   this.state = flockIPhotoAPI.LOGGED_OUT;
  231.  
  232.   this.acUtils = Components.classes["@flock.com/account-utils;1"]
  233.                            .getService(Components.interfaces.flockIAccountUtils);
  234.  
  235.   this.url = "http://www.flickr.com";
  236.   this.mIsInitialized = false;
  237.   this._ctk = {
  238.     interfaces: [
  239.       "nsISupports",
  240.       "nsIClassInfo",
  241.       "nsISupportsCString",
  242.       "nsIObserver",
  243.       "flockIPhotoAPI",
  244.       "flockIPollingService",
  245.       //"flockIPeopleActionController",
  246.       "flockIWebService",
  247.       "flockIAuthenticateNewAccount",
  248.       "flockIManageableWebService",
  249.       "flockIMediaWebService",
  250.       "flockISocialWebService",
  251.     ],
  252.     shortName: "flickr",
  253.     fullName: "Flickr",
  254.     description: FLICKR_TITLE,
  255.     favicon: FLICKR_FAVICON,
  256.     CID: FLICKR_CID,
  257.     contractID: FLICKR_CONTRACTID,
  258.     accountClass: flickrAccount,
  259.     needPassword: false
  260.   };
  261.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  262.   this._logger.init('flickr');
  263.   this._profiler = Cc["@flock.com/profiler;1"].getService(Ci.flockIProfiler);
  264.   this.init();
  265. }
  266.  
  267.  
  268. // BEGIN nsIObserver interface
  269. flickrService.prototype.observe =
  270. function flickrService_observe(aSubject, aTopic, aData)
  271. {
  272.   switch (aTopic) {
  273.     case "new-feed-items":
  274.     {
  275.       aSubject.QueryInterface(Components.interfaces.flockIFeed);
  276.       var uri = aSubject.getURL();
  277.       if (uri.host == "flickr.com" || (uri.host.indexOf(".flickr.com") > 0)) {
  278.         if (this.acUtils.activatedAccountExists(this.urn)) {
  279.           this._handleCommentNotifications(aSubject);
  280.         }
  281.       }
  282.     }; break;
  283.   }
  284. }
  285. // END nsIObserver interface
  286.  
  287.  
  288. flickrService.prototype._handleCommentNotifications =
  289. function flickrService_handleCommentNotifications(aFeed)
  290. {
  291.   // return; // FIXME turn this back on later
  292.   this._logger.info("JMC: Handling comment notification...");
  293.   aFeed.QueryInterface(Components.interfaces.flockIFeed);
  294.  
  295.   var feedURL = aFeed.getURL().spec;
  296.   feedURL.match(/\.gne\?.+id=(.+?)&/);
  297.   var aAccountID = RegExp.$1;
  298.   var account = this.faves_coop.get("urn:flock:flickr:account:" + aAccountID);
  299.   // For each unread item,
  300.   // Create a notification
  301.   // If there's already a notification for that photo,
  302.   // -- if the notification is unread, add to it
  303.   // -- otherwise, replace it and mark it unread again
  304.   var commentEnums = aFeed.getUnreadItems();
  305.   while (commentEnums.hasMoreElements()) {
  306.     var comment = commentEnums.getNext();
  307.     comment.QueryInterface(Components.interfaces.flockIFeedItem);
  308.  
  309.     // If the comment is by me, don't notify
  310.     if (comment.getAuthor().indexOf(account.name) > -1) {
  311.       comment.setRead(true);
  312.       continue;
  313.     }
  314.  
  315.     var commenturl = comment.getLink();
  316.     commenturl = commenturl.spec;
  317.     // http://www.flickr.com/photos/join_the_flock/49599935/#comment72157594348910818
  318.     commenturl.match(/\/photos\/.+\/(\d+)\/\#/);
  319.     var photoid = RegExp.$1;
  320.  
  321.     var titlestring = comment.getTitle();
  322.     titlestring.match(/Comment about (.+)/);
  323.     var photoTitle = RegExp.$1;
  324.  
  325.     var notificationStream = "urn:flock:flickr:account:"+aAccountID+":notifications";
  326.     var notificationurn = "urn:flock:flickr:account:"+aAccountID+":notification:photo:"+photoid;
  327.     var notif = this.faves_coop.get(notificationurn);
  328.     if (notif) {
  329.       if (!notif.unseen) {
  330.         notif.URL = commenturl;
  331.         notif.unseen = true;
  332.       }
  333.       notif.title = "'" + photoTitle + "' - New comment on Flickr";
  334.       notif.description = "There is a new comment about '" + photoTitle  + "'";
  335.     } else {
  336.       var nProps = Components.classes["@mozilla.org/hash-property-bag;1"]
  337.                           .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  338.       nProps.setPropertyAsAString("favicon", FLICKR_FAVICON);
  339.       nProps.setPropertyAsAString("type", "PhotoComment");
  340.       nProps.setPropertyAsAString("name", "PhotoComment");
  341.       nProps.setPropertyAsAString("description", "There is a new comment about '" + photoTitle  + "'");
  342.       nProps.setPropertyAsAString("title", "'" + photoTitle + "' - New comment on Flickr");
  343.       nProps.setPropertyAsAString("URL", commenturl);
  344.       nProps.setPropertyAsAString("unseen", "true");
  345.       this.acUtils.raiseNotification( notificationStream,
  346.                                       notificationurn,
  347.                                       nProps );
  348.     }
  349.     comment.setRead(true);
  350.   }
  351. }
  352.  
  353.  
  354. flickrService.prototype.serviceName = "Flickr";
  355. flickrService.prototype.shortName = "flickr";
  356.  
  357. flickrService.prototype.handleInfoResult = function(aXML) {
  358.   var photo = aXML.getElementsByTagName("photo")[0];
  359.   var owner = aXML.getElementsByTagName("owner")[0];
  360.   var visibility = aXML.getElementsByTagName("visibility")[0];
  361.   var title = aXML.getElementsByTagName("title")[0].firstChild.nodeValue;
  362.   var dates =  aXML.getElementsByTagName("dates")[0];
  363.   var id = photo.getAttribute("id");
  364.   var server = photo.getAttribute("server");
  365.   var secret = photo.getAttribute("secret");
  366.   var lastUpdate = dates.lastupdate;
  367.   var uploadDate = dates.posted;
  368.   var square_url = flock_flickrBuildPhotoUrl(server,id,secret,'square');
  369.   var small_url = flock_flickrBuildPhotoUrl(server,id,secret,'small');
  370.   var med_url = flock_flickrBuildPhotoUrl(server,id,secret,'medium');
  371.   var page_url = flock_flickrBuildPageUrl(owner.getAttribute('nsid'),id);
  372.   var icon_server = photo.getAttribute("iconserver");
  373.   var newPhoto = new flickrPhoto();
  374.   newPhoto.webPageUrl = page_url;
  375.   newPhoto.thumbnail = square_url;
  376.   newPhoto.midSizePhoto = small_url;
  377.   newPhoto.largeSizePhoto = med_url;
  378.   newPhoto.username = owner.getAttribute('username');
  379.   newPhoto.title = title;
  380.   newPhoto.id = id;
  381.   newPhoto.lastUpdate = lastUpdate;
  382.   newPhoto.uploadDate = parseInt(uploadDate)*1000;
  383.   if (icon_server == '1') {
  384.     newPhoto.icon = 'http://www.flickr.com/images/buddyicon.jpg';
  385.   }
  386.   else {
  387.     newPhoto.icon = 'http://static.flickr.com/' + icon_server + '/buddyicons/' + owner +'.jpg';
  388.   }
  389.   var ispublic = visibility.getAttribute("ispublic");
  390.   if (ispublic =="1") ispublic = "true";
  391.   else ispublic = "false";
  392.   newPhoto.is_public = ispublic;
  393.   newPhoto.is_video = false;
  394.   return newPhoto;
  395. }
  396.  
  397. flickrService.prototype.handlePhotosResult =
  398. function (aXML, aUserid)
  399. {
  400.   var rval = [];
  401.   var photoList = aXML.getElementsByTagName("photo");
  402.   for (var i = 0; i < photoList.length; i++) {
  403.     var photo = photoList[i];
  404.  
  405.     var id = photo.getAttribute("id");
  406.     var server = photo.getAttribute("server");
  407.     var secret = photo.getAttribute("secret");
  408.     var title = photo.getAttribute("title");
  409.     var owner = photo.getAttribute("owner");
  410.     if (!owner) owner = aUserid;
  411.     var owner_name = photo.getAttribute("ownername");
  412.     var date_upload = photo.getAttribute("dateupload");
  413.     var date_added = photo.getAttribute("dateadded");
  414.     
  415.     var icon_server = photo.getAttribute("iconserver");
  416.     var square_url = flock_flickrBuildPhotoUrl(server,id,secret,'square');
  417.     var small_url = flock_flickrBuildPhotoUrl(server,id,secret,'small');
  418.     var med_url = flock_flickrBuildPhotoUrl(server,id,secret,'medium');
  419.     var page_url = flock_flickrBuildPageUrl(owner,id);
  420.     var newPhoto = new flickrPhoto();
  421.     newPhoto.webPageUrl = page_url;
  422.     newPhoto.thumbnail = square_url;
  423.     newPhoto.midSizePhoto = small_url;
  424.     newPhoto.largeSizePhoto = med_url;
  425.     newPhoto.username = owner_name;
  426.     newPhoto.userid = owner;
  427.     newPhoto.title = title;
  428.     newPhoto.id = id;
  429.     if (icon_server == '1') {
  430.       newPhoto.icon = 'http://www.flickr.com/images/buddyicon.jpg';
  431.     }
  432.     else {
  433.       newPhoto.icon = 'http://static.flickr.com/' + icon_server + '/buddyicons/' + owner +'.jpg';
  434.     }
  435.     newPhoto.uploadDate = parseInt((date_added) ? date_added : date_upload)*1000;
  436.  
  437.     // if the photo result set doesn't return back the ispublic
  438.     // attribute, we assume that the server is returning only
  439.     // public photos back in the response
  440.     if (photo.hasAttribute("ispublic")) {
  441.       var ispublic = photo.getAttribute("ispublic");
  442.       if (ispublic =="1") ispublic = "true";
  443.       else ispublic = "false";
  444.     } else {
  445.       ispublic = "true";
  446.     }
  447.  
  448.     newPhoto.is_public = ispublic;
  449.     newPhoto.is_video = false;
  450.  
  451.     rval.push(newPhoto);
  452.   }
  453.   return rval;
  454. }
  455.  
  456. flickrService.prototype.getPhotoFromRDFNode =
  457. function (aRDFId)
  458. {
  459.   var newPhoto = new flickrPhoto();
  460.   var coopPhoto = this.faves_coop.get(aRDFId);
  461.   newPhoto.webPageUrl = coopPhoto.URL;
  462.   newPhoto.thumbnail = coopPhoto.thumbnail;
  463.   newPhoto.midSizePhoto = coopPhoto.midSizePhoto;
  464.   newPhoto.largeSizePhoto = coopPhoto.largeSizePhoto;
  465.   newPhoto.username = coopPhoto.username;
  466.   newPhoto.userid = coopPhoto.userid;
  467.   newPhoto.title = coopPhoto.name;
  468.   newPhoto.id = coopPhoto.photoid;
  469.   newPhoto.icon = coopPhoto.favicon;
  470.   newPhoto.uploadDate = coopPhoto.datevalue;
  471.   newPhoto.is_public = coopPhoto.is_public;
  472.   newPhoto.is_video = coopPhoto.is_video;
  473.   return newPhoto;
  474. }
  475.  
  476. flickrService.prototype.handleContactsResult =
  477. function (aXML)
  478. {
  479.   var rval = [];
  480.   var contactList = aXML.getElementsByTagName("contact");
  481.   for (var i = 0; i < contactList.length; i++) {
  482.     var contact = contactList[i];
  483.     var nsid = contact.getAttribute("nsid");
  484.     var username = contact.getAttribute("username");
  485.     var fullname = contact.getAttribute("fullname");
  486.     var family = contact.getAttribute("family");
  487.     var friend = contact.getAttribute("friend");
  488.     var iconserver = contact.getAttribute("iconserver");
  489.  
  490.     if (true || family == "1" || friend == "1") {
  491.       this._logger.info(".handleContactsResult(): username="+username+" family="+family+" friend="+friend);
  492.  
  493.       // implement flockIPhotoPerson
  494.       //var newContact = {};
  495.       var newContact = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID]
  496.                                  .createInstance(flockIPhotoPerson);
  497.       newContact.id = nsid;
  498.       newContact.username = username;
  499.       newContact.fullname = username;
  500.       newContact.service = this;
  501.       newContact.avatarUrl = 'http://static.flickr.com/'+iconserver+'/buddyicons/'+nsid+'.jpg'
  502.       rval.push(newContact);
  503.     }
  504.   }
  505.   return rval;
  506. }
  507.  
  508. flickrService.prototype.handleAlbumsResult =
  509. function (aXML)
  510. {
  511.   var rval = [];
  512.   var photosetList = aXML.getElementsByTagName("photoset");
  513.   var titleList = aXML.getElementsByTagName("title");
  514.   for (var i = 0; i < photosetList.length; i++) {
  515.     var id = photosetList[i].getAttribute("id");
  516.     var title = titleList[i].firstChild.nodeValue;
  517.  
  518.     var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID]
  519.                              .createInstance(flockIPhotoAlbum);
  520.     newAlbum.id = id;
  521.     newAlbum.title = title;
  522.  
  523.     rval.push(newAlbum);
  524.   }
  525.   return rval;
  526. }
  527.  
  528. flickrService.prototype.getContacts =
  529. function flickrService_getContacts(aListener)
  530. {
  531.   this._logger.info(".getContacts(...)");
  532.   var inst = this;
  533.   var myListener = {
  534.     onResult: function (aXML) {
  535.       var rval = inst.handleContactsResult(aXML);
  536.       var enum_ = {
  537.         hasMoreElements: function() {
  538.           return (rval.length > 0);
  539.         },
  540.         getNext: function() {
  541.           return rval.shift();
  542.         }
  543.       }
  544.       aListener.onGetContactsResult(enum_);
  545.     },
  546.     onError: function (aError) {
  547.       aListener.onError(aError);
  548.     }
  549.   }
  550.   var params = {};
  551.   var dict = params2Dictionary(params);
  552.   this.authenticatedCall(myListener, "flickr.contacts.getList", dict);
  553. }
  554.  
  555.  
  556. flickrService.prototype.getFriendsContacts =
  557. function flickrService_getFriendsContacts(aListener, aUserID)
  558. {
  559.   this._logger.info(".getFriendsContacts(...)");
  560.   var inst = this;
  561.   var myListener = {
  562.     onResult: function (aXML) {
  563.       var rval = inst.handleContactsResult(aXML);
  564.       var enum_ = {
  565.         hasMoreElements: function() {
  566.           return (rval.length>0);
  567.         },
  568.         getNext: function() {
  569.           return rval.shift();
  570.         }
  571.       }
  572.       aListener.onGetContactsResult(enum_);
  573.     },
  574.     onError: function (aError) {
  575.       aListener.onError(aError);
  576.     }
  577.   }
  578.   var params = {};
  579.   params.user_id = aUserID;
  580.   var dict = params2Dictionary(params);
  581.   this.call(myListener, "flickr.contacts.getPublicList", dict);
  582. }
  583.  
  584. flickrService.prototype.getPhoto =
  585. function (aListener, aID)
  586. {
  587.   var inst = this;
  588.   var myListener = {
  589.     onResult: function (aXML) {
  590.       var photo = inst.handleInfoResult(aXML);
  591.       aListener.onGetPhotoResult(photo);
  592.     },
  593.     onError: function (aError) {
  594.       aListener.onError(aError);
  595.     }
  596.   }
  597.   var params = {};
  598.   params.photo_id = aID;
  599.   var dict = params2Dictionary(params);
  600.   if (this.getAuthUser()) {
  601.     this.authenticatedCall(myListener, "flickr.photos.getInfo", dict);
  602.   } else {
  603.     this.call(myListener, "flickr.photos.getInfo", dict);
  604.   }
  605. }
  606.  
  607. flickrService.prototype.fakeAlbums = {};
  608. flickrService.prototype.realishAlbums = {};
  609.  
  610. flickrService.prototype.createAlbum =
  611. function (aListener, aTitle)
  612. {
  613.   // trim white space from front and end of string
  614.   aTitle = aTitle.replace(/^\s+|\s+$/g, "");
  615.   if (aTitle) {
  616.     var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID]
  617.                              .createInstance(flockIPhotoAlbum);
  618.     newAlbum.title = aTitle;
  619.     var date = new Date();  //hopefully this won't collide with an actual id!
  620.     newAlbum.id = date.getTime();
  621.     this.fakeAlbums[newAlbum.id] = newAlbum;
  622.     aListener.onCreateAlbum(newAlbum);
  623.   } else {
  624.     var error = Components.classes[FLOCK_ERROR_CONTRACTID].createInstance(flockIError);
  625.     error.errorCode = error.PHOTOSERVICE_EMPTY_ALBUMNAME;
  626.     aListener.onError(error);
  627.   }
  628. }
  629.  
  630. flickrService.prototype.reallyCreateAlbum =
  631. function (aListener, aTitle, aID)
  632. {
  633.   var inst = this;
  634.   var myListener = {
  635.     onResult: function (aXML) {
  636.       var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID]
  637.                                .createInstance(flockIPhotoAlbum);
  638.       var photoset = aXML.getElementsByTagName("photoset")[0];
  639.       newAlbum.title = aTitle;
  640.       newAlbum.id = photoset.getAttribute("id");;
  641.       aListener.onCreateAlbumResult(newAlbum);
  642.     },
  643.     onError: function (aXML) {
  644.       aListener.onError(aXML);
  645.     }
  646.   }
  647.   var params = {};
  648.   params.title = aTitle;
  649.   params.primary_photo_id = aID;
  650.   var dict = params2Dictionary(params);
  651.   this.authenticatedCall(myListener, "flickr.photosets.create", dict);
  652. }
  653.  
  654. flickrService.prototype.getAlbums =
  655. function flickrService_getAlbums(aListener, aUsername)
  656. {
  657.   var inst = this;
  658.   var myListener = {
  659.     onResult: function (aXML) {
  660.       try {
  661.       var rval = inst.handleAlbumsResult(aXML);
  662.       var enum_ = {
  663.         hasMoreElements: function() {
  664.           return (rval.length>0);
  665.         },
  666.         getNext: function() {
  667.           return rval.shift();
  668.         }
  669.       }
  670.       aListener.onGetAlbumsResult(enum_);
  671.       } catch(e) {
  672.         aListener.onError(null);
  673.       }
  674.     },
  675.     onError: function (aXML) {
  676.       aListener.onError(aXML);
  677.     }
  678.   }
  679.   var params = {};
  680.   if (aUsername) {
  681.     params.user_id  = aUsername;
  682.   }
  683.   var dict = params2Dictionary(params);
  684.   if (this.getAuthUser()) {
  685.     this.authenticatedCall(myListener, "flickr.photosets.getList", dict);
  686.   } else {
  687.     this.call(myListener, "flickr.photosets.getList", dict);
  688.   }
  689. }
  690.  
  691. flickrService.prototype.getAccountStatus =
  692. function flickrService_getAccountStatus(aListener)
  693. {
  694.   var inst = this;
  695.   var myListener = {
  696.     onResult:function (aXML) {
  697.       try {
  698.         inst._logger.info('we got a result for account status...');
  699.         var photoAccount = Components.classes[FLOCK_PHOTO_ACCOUNT_CONTRACTID].createInstance(flockIPhotoAccount);
  700.         var username = aXML.getElementsByTagName('username')[0].firstChild.nodeValue;
  701.         var bandwidth = aXML.getElementsByTagName('bandwidth')[0];
  702.         var maxFileSize = aXML.getElementsByTagName('filesize')[0];
  703.         var isPro = aXML.getElementsByTagName('user')[0].getAttribute('ispro');
  704.         photoAccount.username = username;
  705.         photoAccount.maxSpace = bandwidth.getAttribute('max');
  706.         photoAccount.usedSpace = bandwidth.getAttribute('used');
  707.         photoAccount.maxFileSize = maxFileSize;
  708.         photoAccount.usageUnits = "bytes";
  709.         photoAccount.isPremium = (isPro == "1" ? true: false);
  710.         aListener.onGetAccountStatus(photoAccount);
  711.       } catch (ex) {
  712.         inst._logger.info('getaccountstatus error >>>>>>>>>>>>>>>>>>>>' + ex);
  713.       }
  714.     },
  715.     onError: function (aError) {
  716.       aListener.onError(aError);
  717.     }
  718.   }
  719.   var params = {};
  720.   var dict = params2Dictionary(params);
  721.   this.authenticatedCall(myListener, "flickr.people.getUploadStatus", dict);
  722. }
  723.  
  724. flickrService.prototype.getContactsPhotos =
  725. function flickrService_getContactsPhotos(aListener)
  726. {
  727.   // return; // XXX TODO FIXME (JMC): Save me from contacts' photos hanging my browser!
  728.   var inst = this;
  729.   var myListener = {
  730.     onResult: function (aXML) {
  731.       var rval = inst.handlePhotosResult(aXML);
  732.       var enum_ = {
  733.         hasMoreElements: function() {
  734.           return (rval.length>0);
  735.         },
  736.         getNext: function() {
  737.           return rval.shift();
  738.         }
  739.       }
  740.       aListener.onSearchResult(enum_);
  741.     },
  742.     onError: function (aXML) {
  743.       aListener.onError(null);
  744.     }
  745.   }
  746.   var params = {};
  747.   params.single_photo = "1";
  748.   params.include_self = "1";
  749.   params.extras = "owner_name,license,date_upload,icon_server";
  750.   var dict = params2Dictionary(params);
  751.   this.authenticatedCall(myListener, "flickr.photos.getContactsPhotos", dict);
  752. }
  753.  
  754. flickrService.prototype.getMostRecentPhotoForList =
  755. function flickrService_getMostRecentPhotoForList(aListener, aEnumerator)
  756. {
  757.   var mg = new MultiGetter();
  758.   mg.init(this, aListener, aEnumerator);
  759. }
  760.  
  761. flickrService.prototype.findByUsername =
  762. function flickrService_findByUsername(aListener, aUsername)
  763. {
  764.   var inst = this;
  765.   var userListener = function(aListener) {
  766.     this.mListener = aListener;
  767.   }
  768.   userListener.prototype.onGetInfo = function (aPerson) {
  769.     this.mListener.onFindByUsernameResult(aPerson);
  770.   }
  771.   userListener.prototype.onError = function (aError) {
  772.     inst._logger.info(aError);
  773.     this.mListener.onError(aError);
  774.   }
  775.   var myListener = {
  776.     onResult: function (aXML) {
  777.       var user = aXML.getElementsByTagName("user")[0];
  778.       var id = user.getAttribute("id");
  779.       inst.people_getInfo(new userListener(aListener), id);
  780.     },
  781.     onError: function (aXML) {
  782.       aListener.onError(aXML);
  783.     }
  784.   }
  785.   var params = {};
  786.   params.username = aUsername;
  787.   var dict = params2Dictionary(params);
  788.   this.call(myListener, "flickr.people.findByUsername", dict);
  789. }
  790.  
  791.  
  792. flickrService.prototype.lookupUser =
  793. function (aListener, aURL)
  794. {
  795.   var inst = this;
  796.   var myListener = {
  797.     onResult: function (aXML) {
  798.       var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID]
  799.                                  .createInstance(flockIPhotoPerson);
  800.       var user = aXML.getElementsByTagName("user")[0];
  801.       newUserObj.service = inst;
  802.       newUserObj.id = user.getAttribute("id");
  803.       newUserObj.username = user.getElementsByTagName("username")[0].childNodes[0].nodeValue;
  804.       newUserObj.fullname = newUserObj.username;
  805.       aListener.onLookupUser(newUserObj);
  806.     },
  807.     onError: function (aXML) {
  808.       aListener.onError(aXML);
  809.     }
  810.   }
  811.   var params = {};
  812.   params.url = aURL;
  813.   var dict = params2Dictionary(params);
  814.   this.call(myListener, "flickr.urls.lookupUser", dict);
  815. }
  816.  
  817. flickrService.prototype.getValidPerson =
  818. function (aListener, aURL)
  819. {
  820.   this.lookupUser( {
  821.     onLookupUser: aListener.onGetValidPerson,
  822.     onError: aListener.onError
  823.   }, aURL );
  824. }
  825.  
  826. flickrService.prototype.people_getInfo =
  827. function (aListener, aUserId)
  828. {
  829.   var inst = this;
  830.   var myListener = {
  831.     onResult: function (aXML) {
  832.       var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID].createInstance(flockIPhotoPerson);
  833.       var person = aXML.getElementsByTagName("person")[0];
  834.       var iconserver = person.getAttribute("iconserver");
  835.       var count = person.getElementsByTagName("photos")[0].getElementsByTagName("count")[0].firstChild.nodeValue;
  836.       newUserObj.service = inst;
  837.       newUserObj.id = person.getAttribute("id");
  838.       newUserObj.username = person.getElementsByTagName('username')[0].firstChild.nodeValue + '';
  839.       newUserObj.fullname = newUserObj.username;
  840.       newUserObj.avatarUrl = 'http://static.flickr.com/'+iconserver+'/buddyicons/'+newUserObj.id+'.jpg'
  841.       newUserObj.photoCount = parseInt(count);
  842.       aListener.onGetInfo(newUserObj);
  843.     },
  844.     onError: function (aXML) {
  845.       aListener.onError(aXML);
  846.     }
  847.   }
  848.   var params = {};
  849.   params.user_id = aUserId;
  850.   var dict = params2Dictionary(params);
  851.   this.call(myListener, "flickr.people.getInfo", dict);
  852. }
  853.  
  854. flickrService.prototype.queryChannel =
  855. function flickrService_queryChannel(aListener, aQueryString, aCount, aPage)
  856. {
  857.   var aQuery = new queryHelper(aQueryString);
  858.   // return; // XXX TODO FIXME: This is killing performance!
  859.   var inst=this;
  860.   var myListener = {
  861.     onResult: function (aXML) {
  862.       var rval = inst.handlePhotosResult(aXML);
  863.       var enum_ = {
  864.         hasMoreElements: function() {
  865.           return (rval.length>0);
  866.         },
  867.         getNext: function() {
  868.           return rval.shift();
  869.         }
  870.       }
  871.       aListener.onSearchResult(enum_);
  872.     },
  873.     onError: function (aError) {
  874.       aListener.onError(aError);
  875.     }
  876.   }
  877.  
  878.   var params = {
  879.     per_page: aCount,
  880.     page: aPage,
  881.     extras: "owner_name,icon_server,date_upload,license"
  882.   };
  883.  
  884.   if (aQuery.getKey('special') == 'recent') {
  885.     if (aQuery.hasKey('search')) {
  886.       params.text = aQuery.getKey('search');
  887.       var dict = params2Dictionary(params);
  888.       this.call(myListener, "flickr.photos.search", dict);
  889.     } else {
  890.       var dict = params2Dictionary(params);
  891.       this.call(myListener, "flickr.photos.getRecent", dict)
  892.     }
  893.   }
  894.  
  895.   if (aQuery.getKey("special") == 'interestingness') {
  896.     if (aQuery.hasKey('search')) {
  897.       params.text = aQuery.getKey('search');
  898.       params.sort = 'interestingness-desc';
  899.       var dict = params2Dictionary(params);
  900.       this.call(myListener, "flickr.photos.search", dict);
  901.     } else {
  902.       var dict = params2Dictionary(params);
  903.       this.call(myListener, "flickr.interestingness.getList", dict)
  904.     }
  905.   }
  906.  
  907.   if (aQuery.hasKey('search')) {
  908.      aText = aQuery.getKey('search');
  909.      if (aText && aText.length) {
  910.        params.text = aText;
  911.        var dict = params2Dictionary(params);
  912.        this.call(myListener, "flickr.photos.search", dict);
  913.      }
  914.    }
  915. }
  916.  
  917. flickrService.prototype.poolSearch =
  918. function flickrService_poolSearch(aListener, aQueryString, aCount, aPage)
  919. {
  920.   var aQuery = new queryHelper(aQueryString);
  921.   var inst = this;
  922.   var myListener = {
  923.     onResult: function (aXML) {
  924.       var rval = inst.handlePhotosResult(aXML);
  925.       var enum_ = {
  926.         hasMoreElements: function() {
  927.           return (rval.length>0);
  928.         },
  929.         getNext: function() {
  930.           return rval.shift();
  931.         }
  932.       }
  933.       aListener.onSearchResult(enum_);
  934.     },
  935.     onError: function (aError) {
  936.       aListener.onError(aError);
  937.     }
  938.   }
  939.  
  940. /*
  941.   var aText;
  942.  
  943.   if (aQuery.split('?').length > 1) {
  944.     aText = aQuery.split('?')[1];
  945.     aQuery = aQuery.split('?')[0];
  946.   }
  947.  
  948.   aQuery = aQuery.split(':')[1]
  949.   */
  950.  
  951.   var params = {
  952.     group_id: aQuery.pool,
  953.     per_page: aCount,
  954.     page: aPage,
  955.     extras: "owner_name,icon_server,date_upload,license"
  956.   };
  957.  
  958.   if (aQuery.search) {
  959.     params.tags = aQuery.search;
  960.     var dict = params2Dictionary(params);
  961.     this.call(myListener, "flickr.groups.pools.getPhotos", dict);
  962.   } else {
  963.     var dict = params2Dictionary(params);
  964.     this.call(myListener, "flickr.groups.pools.getPhotos", dict)
  965.   }
  966. }
  967.  
  968. flickrService.prototype.search =
  969. function flickrService_search(aListener, aQueryString, aCount, aPage)
  970. {
  971.   // return; // FIXME - turn this back on later
  972.   var aQuery = new queryHelper(aQueryString);
  973.   if (aQuery.pool) {
  974.       this.poolSearch(aListener, aQueryString, aCount, aPage);
  975.       return;
  976.     }
  977.   if (!aQuery.user) {
  978.       this.queryChannel(aListener, aQueryString, aCount, aPage);
  979.       return;
  980.   }
  981.   var aUserid = aQuery.user;
  982.   var inst = this;
  983.   var myListener = {
  984.     onResult: function (aXML) {
  985.       var rval = inst.handlePhotosResult(aXML, aUserid);
  986.       var enum_ = {
  987.         hasMoreElements: function() {
  988.           return (rval.length>0);
  989.         },
  990.         getNext: function() {
  991.           return rval.shift();
  992.         }
  993.       }
  994.       aListener.onSearchResult(enum_);
  995.     },
  996.     onError: function (aError) {
  997.       aListener.onError(aError);
  998.     }
  999.   }
  1000.   // Flickr return the photo in the reverse side when calling flickr.photosets.getPhotos!
  1001.   // So we need to enumerate backward :-/
  1002.   var myPhotosetListener = {
  1003.     onResult: function (aXML) {
  1004.       var rval = inst.handlePhotosResult(aXML, aUserid);
  1005.       var enum_ = {
  1006.         hasMoreElements: function() {
  1007.           return (rval.length>0);
  1008.         },
  1009.         getNext: function() {
  1010.           return rval.pop();
  1011.         }
  1012.       }
  1013.       aListener.onSearchResult(enum_);
  1014.     },
  1015.     onError: function (aError) {
  1016.       aListener.onError(aError);
  1017.     }
  1018.   }
  1019.  
  1020.   var params = {};
  1021.   if (aUserid && aUserid.length) params.user_id = aUserid;
  1022.   params.per_page = aCount;
  1023.   params.page = aPage;
  1024.  
  1025.   if (aQuery.search) params.text = aQuery.search;
  1026.  
  1027.   params.tag_mode = "all";
  1028.   params.extras = "owner_name,license,date_upload,icon_server";
  1029.   if (aQuery.album) {
  1030.     params.photoset_id = aQuery.album;
  1031.   }
  1032.  
  1033.   var dict = params2Dictionary(params);
  1034.   if (params.photoset_id) {
  1035.     if (aPage > 1) return; // this is because the flickr call doesn't support pagination [bug 4770]
  1036.     dict = params2Dictionary(params);
  1037.     if (this.getAuthUser())
  1038.       this.authenticatedCall(myPhotosetListener, "flickr.photosets.getPhotos", dict);
  1039.     else
  1040.       this.call(myPhotosetListener, "flickr.photosets.getPhotos", dict);
  1041.   } else if (params.user_id || params.text) {
  1042.     if (this.isLoggedIn) this.authenticatedCall(myListener, "flickr.photos.search", dict);
  1043.     else this.call(myListener, "flickr.photos.search", dict);
  1044.   }
  1045. }
  1046.  
  1047. flickrService.prototype.supportsSearch =
  1048. function flickrService_supportsSearch( aQueryString ) {
  1049.   var aQuery = new queryHelper(aQueryString);
  1050.  
  1051.   if (aQuery.special) {
  1052.     var channel = channels["special:" + aQuery.special];
  1053.     if (channel) {
  1054.       return channel.supportsSearch;
  1055.     }
  1056.   }
  1057.  
  1058.   if (aQuery.album) return false;
  1059.   if (aQuery.user) return true;
  1060.   if (aQuery.search) return false;
  1061.   return false;
  1062. }
  1063.  
  1064. var channels = {
  1065.   "special:recent": {
  1066.     title: "Recent Public Photos",
  1067.     supportsSearch: false
  1068.   },
  1069.   "special:interestingness": {
  1070.     title: "Interestingness",
  1071.     supportsSearch: true
  1072.   }
  1073. }
  1074.  
  1075. flickrService.prototype.getChannel =
  1076. function flickrService_getChannel(aChannelId)
  1077. {
  1078.   if (!(aChannelId in channels)) return null;
  1079.  
  1080.   var nc = Components.classes["@flock.com/media-channel;1"]
  1081.                        .createInstance(Components.interfaces.flockIMediaChannel);
  1082.   nc.id = aChannelId;
  1083.   nc.title = channels[aChannelId].title
  1084.   nc.supportsSearch = channels[aChannelId].supportsSearch;
  1085.   return nc;
  1086. }
  1087.  
  1088. flickrService.prototype.__defineGetter__("channels", function () {
  1089.   var ar = [];
  1090.  
  1091.   for (var id in channels) {
  1092.     var nc = Components.classes["@flock.com/media-channel;1"]
  1093.                        .createInstance(Components.interfaces.flockIMediaChannel);
  1094.     nc.id = id;
  1095.     nc.title = channels[id].title
  1096.     nc.supportsSearch = channels[id].supportsSearch;
  1097.     ar.push(nc);
  1098.   }
  1099.  
  1100.   var rval = {
  1101.     getNext: function() {
  1102.       var rval = ar.shift();
  1103.       return rval;
  1104.     },
  1105.     hasMoreElements: function() {
  1106.       return (ar.length>0);
  1107.     }
  1108.   }
  1109.   return rval;
  1110. })
  1111.  
  1112. flickrService.prototype.mSupportsTitle = true;
  1113. flickrService.prototype.mSupportsTags = true;
  1114. flickrService.prototype.mSupportsContacts = true;
  1115. flickrService.prototype.mSupportsPrivacy = true;
  1116. flickrService.prototype.iconUrl = "chrome://browser/skin/flock/photo/flickrIcon.png";
  1117.  
  1118. flickrService.prototype.supportsFeature =
  1119. function flickrService_supportsFeature(aFeature)
  1120. {
  1121.   var supports = {};
  1122.   supports.tags = true;
  1123.   supports.title = true;
  1124.   supports.fileName = false;
  1125.   supports.contacts = true;
  1126.   supports.privacy = true;
  1127.   supports.albumCreation = true;
  1128.   return (supports[aFeature] == true);
  1129. }
  1130.  
  1131. flickrService.prototype.init =
  1132. function flickrService_init()
  1133. {
  1134.   // Prevent re-entry
  1135.   if (this.mIsInitialized) return;
  1136.   this.mIsInitialized = true;
  1137.  
  1138.   this._logger.info(".init()");
  1139.   var evtID = this._profiler.profileEventStart("flickr-init");
  1140.  
  1141.   // Determine whether this service has been disabled, and unregister if so.
  1142.   this.prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
  1143.   if ( this.prefService.getPrefType(SERVICE_ENABLED_PREF) &&
  1144.        !this.prefService.getBoolPref(SERVICE_ENABLED_PREF) )
  1145.   {
  1146.     this._logger.info("Pref "+SERVICE_ENABLED_PREF+" set to FALSE... not initializing.");
  1147.     var catMgr = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
  1148.     catMgr.deleteCategoryEntry("wsm-startup", CATEGORY_COMPONENT_NAME, true);
  1149.     catMgr.deleteCategoryEntry("flockIPhotoAPI", CATEGORY_ENTRY_NAME, true);
  1150.     catMgr.deleteCategoryEntry("flockWebService", CATEGORY_ENTRY_NAME, true);
  1151.     return;
  1152.   }
  1153.  
  1154.   this.api = new FlickrAPI();
  1155.   this.api.svc = this;
  1156.   var inst = this;
  1157.  
  1158.   this.faves_coop = Components.classes["@flock.com/singleton;1"]
  1159.                               .getService(Components.interfaces.flockISingleton)
  1160.                               .getSingleton("chrome://browser/content/flock/common/load-faves-coop.js")
  1161.                               .wrappedJSObject;
  1162.  
  1163.   this.obs.addObserver(this, "new-feed-items", false);
  1164.  
  1165.   this.urn = "urn:flickr:service";
  1166.   this.svcCoopObj = new this.faves_coop.Service(
  1167.     this.urn,
  1168.     {
  1169.       name: "flickr",
  1170.       desc: "The Flickr Service",
  1171.       contactLabel: 'Contacts'
  1172.     }
  1173.   );
  1174.   this.svcCoopObj.serviceId = FLICKR_CONTRACTID;
  1175.  
  1176.   // Load Web Detective file
  1177.   this.webDetective = this.acUtils.useWebDetective("flickr.xml");
  1178.   for (var s in gStrings) {
  1179.     gStrings[s] = this.webDetective.getString("flickr", s, gStrings[s]);
  1180.   }
  1181.   this.svcCoopObj.domains = gStrings["domains"];
  1182.   this.svcCoopObj.loginURL = gStrings["userlogin"];
  1183.  
  1184.   // Update auth states
  1185.   try {
  1186.     if (this.webDetective.detectCookies("flickr", "loggedout", null)) {
  1187.       this.acUtils.markAllAccountsAsLoggedOut(FLICKR_CONTRACTID);
  1188.       this.api.logout();
  1189.       this.state = flockIPhotoAPI.LOGGED_OUT;
  1190.     }
  1191.   } catch (ex) {
  1192.     this._logger.error("ERROR updating auth states for Flickr: "+ex);
  1193.   }
  1194.  
  1195.   var timerFunc = {
  1196.     inProgress: false,
  1197.     count: 0,
  1198.     observe: function(subject, topic, state) {
  1199.       //if (topic == 'nsPref:changed' && state == FLICKR_TOKEN_PREF_ID) {
  1200.       //  var oldToken = flock_getCharPref(FLICKR_TOKEN_PREF_ID);
  1201.       //  if (oldToken && oldToken.length && !inst.api.isLoggedIn()) {
  1202.       //    this.inProgress = true;
  1203.       //    inst.api.checkToken(this, oldToken);
  1204.       //  }
  1205.       //}
  1206.     },
  1207.     notify: function(aTimer) {
  1208.       if (this.inProgress) return;
  1209.       if (inst.api.isLoggedIn()) {
  1210.         inst.api.processPendingUploadTickets();
  1211.         inst.api.processPendingAlbumAdditions();
  1212.       }
  1213.     },
  1214.     onResult: function(aXML) {
  1215.       this.inProgress = false;
  1216.     },
  1217.     onAuth: function(aXML) {
  1218.       this.inProgress = false;
  1219.       inst.state = flockIPhotoAPI.LOGGED_IN;
  1220.     },
  1221.     onError: function(aError) {
  1222.       inst.logout();
  1223.       this.inProgress = false;
  1224.     }
  1225.   }
  1226.   //timerFunc.observe(null, 'nsPref:changed', FLICKR_TOKEN_PREF_ID);
  1227.   timerFunc.notify();
  1228.   //var prefs = Components.classes['@mozilla.org/preferences-service;1']
  1229.   //                      .getService(Components.interfaces.nsIPrefBranch2);
  1230.   //prefs.addObserver(FLICKR_TOKEN_PREF_ID, timerFunc, false);
  1231.   timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
  1232.   timer.initWithCallback(timerFunc, 5 * 1 * 1000, 1);  //re-check token
  1233.  
  1234.   this._profiler.profileEventEnd(evtID, "");
  1235. }
  1236.  
  1237. flickrService.prototype.__defineGetter__('supportsTitle', function () { return this.mSupportsTitle; })
  1238. flickrService.prototype.__defineGetter__('supportsTags', function () { return this.mSupportsTags; })
  1239. flickrService.prototype.__defineGetter__('supportsContacts', function () { return this.mSupportsContacts; })
  1240. flickrService.prototype.__defineGetter__('supportsPrivacy', function () { return this.mSupportsPrivacy; })
  1241.  
  1242. flickrService.prototype.getAuthState =
  1243. function flickrService_getAuthState()
  1244. {
  1245.   return this.state;
  1246. }
  1247.  
  1248. flickrService.prototype.__defineGetter__('authState', function () { return this.state; })
  1249.  
  1250. flickrService.prototype.logout =
  1251. function flickrService_logout()
  1252. {
  1253.   this.api.logout();
  1254.   this.state = flockIPhotoAPI.LOGGED_OUT;
  1255.   this.acUtils.removeCookies(this.webDetective.getSessionCookies("flickr"));
  1256. }
  1257.  
  1258. flickrService.prototype.login =
  1259. function flickrService_login(aAccountURN, aListener)
  1260. {
  1261.   var inst = this;
  1262.   var myListener = {
  1263.     onAuth: function () {
  1264.       inst.state = flockIPhotoAPI.LOGGED_IN;
  1265.       inst.acUtils.ensureOnlyAuthenticatedAccount(aAccountURN);
  1266.       if (aListener) aListener.onAuth();
  1267.     },
  1268.     onError: function (aError) {
  1269.       inst.state = flockIPhotoAPI.LOGIN_ERROR;
  1270.       if (aListener) aListener.onError(aError);
  1271.     }
  1272.   }
  1273.   this.api.logout();
  1274.   this.acUtils.markAllAccountsAsLoggedOut(FLICKR_CONTRACTID);
  1275.   inst.state = flockIPhotoAPI.LOGGING_IN;
  1276.  
  1277.   this.api.login(aAccountURN, myListener);
  1278. }
  1279.  
  1280.  
  1281. flickrService.prototype.__defineGetter__('isLoggedIn', function () { return this.api.isLoggedIn(); })
  1282.  
  1283. flickrService.prototype.getAuthUser = function () {
  1284.   return this.api.getAuthUser();
  1285. }
  1286.  
  1287. flickrService.prototype.getAuthPerson = function () {
  1288.   try {
  1289.     var user = this.api.getAuthUser();
  1290.     var person = {};
  1291.     person.id = user.id;
  1292.     person.fullname = user.fullname;
  1293.     person.username = user.username;
  1294.     person.service = this;
  1295.     return person;
  1296.   }
  1297.   catch(e) {
  1298.     return null;
  1299.   }
  1300. }
  1301.  
  1302. flickrService.prototype.__defineGetter__('isUploading', function () { return this.running; })
  1303.  
  1304. flickrService.prototype.dictionary2Params =
  1305. function flickrService_dictionary2Params(aDictionary)
  1306. {
  1307.   var params = {};
  1308.   var obj = {};
  1309.   var count = {};
  1310.   var keys = aDictionary.getKeys(count, obj);
  1311.   for (var i = 0; i < keys.length;++i) {
  1312.     var supports = aDictionary.getValue(keys[i]);
  1313.     var supportsString = supports.QueryInterface(Components.interfaces.nsISupportsString);
  1314.     var val = supportsString.toString();
  1315.     params[keys[i]] = val;
  1316.   }
  1317.   return params;
  1318. }
  1319.  
  1320. flickrService.prototype.call =
  1321. function flickrService_call(aListener, aMethod, aDictionary)
  1322. {
  1323.   var params = this.dictionary2Params(aDictionary);
  1324.   this.api.call(aListener, aMethod, params);
  1325. }
  1326.  
  1327. flickrService.prototype.authenticatedCall =
  1328. function flickrService_authenticatedCall(aListener, aMethod, aDictionary)
  1329. {
  1330.   if (!this.isLoggedIn) throw "not logged in";
  1331.   var params = this.dictionary2Params(aDictionary);
  1332.   this.api.authenticatedCall(aListener, aMethod, params);
  1333. }
  1334.  
  1335. flickrService.prototype.upload2 =
  1336. function flickrService_upload2(aListener, aUpload, aFile)
  1337. {
  1338.   var params = {};
  1339.   params.title = aUpload.title;
  1340.   params.description = aUpload.description;
  1341.   params.is_family = aUpload.is_family;
  1342.   params.is_friend = aUpload.is_friend;
  1343.   params.is_public = aUpload.is_public;
  1344.   params.async = "1";
  1345.   params.tags = aUpload.tags;
  1346.   this.api.upload(aListener, aFile, params, aUpload);
  1347. }
  1348.  
  1349. flickrService.prototype.cancelUpload =
  1350. function flickrService_cancelUpload()
  1351. {
  1352.   try { this.api.uploader.req.abort(); }
  1353.   catch(e){};
  1354. }
  1355.  
  1356.  
  1357. // *** XXX TODO CDC: Generalize this!
  1358. flickrService.prototype.openSendMessageTabForFill =
  1359. function flickrService_openSendMessageTabForFill(aFriendID, aSubject, aBody)
  1360. {
  1361.   // Get top level window
  1362.   var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
  1363.                                 .getService(Components.interfaces.nsIWindowMediator);
  1364.   var topWin = windowManager.getMostRecentWindow("navigator:browser");
  1365.   var browser = topWin.getBrowser();
  1366.   var newTab = browser.loadOneTab(gStrings["privatemessage"], null, null, null, false, false);
  1367.   var inst = this;
  1368.   var observer = {
  1369.     observe: function(subject, topic, state) {
  1370.       var thisWindow = newTab.linkedBrowser.docShell
  1371.         .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  1372.         .getInterface(Components.interfaces.nsIDOMWindow);
  1373.       if (thisWindow.document == subject) {
  1374.         var form = inst.acUtils.getElementByAttribute(thisWindow.document.documentElement, "action", "messages_write.gne");
  1375.         form.QueryInterface(Components.interfaces.nsIDOMHTMLFormElement);
  1376.         form.elements.namedItem('to_nsid').value = aFriendID;
  1377.         form.elements.namedItem('subject').value = aSubject;
  1378.         form.elements.namedItem('message').value = aBody;
  1379.         inst.obs.removeObserver(this, 'FlockDocumentReady');
  1380.       }
  1381.     }
  1382.   }
  1383.   this.obs.addObserver(observer, 'FlockDocumentReady', false);
  1384. }
  1385.  
  1386.  
  1387. flickrService.prototype.doAction =
  1388. function flickrService_doAction(aActionName, aURN, aSubject)
  1389. {
  1390.   var identityTarget = this.faves_coop.get(aURN);
  1391.  
  1392.   switch (aActionName)
  1393.   {
  1394.     case "uploadPhotos":
  1395.       var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1396.                          .getService(Components.interfaces.nsIWindowMediator);
  1397.       var win = wm.getMostRecentWindow('navigator:browser');
  1398.  
  1399.       if (win) {
  1400.         win.flock_photo.util.launchUploader();
  1401.       }
  1402.       break;
  1403.     default:
  1404.       break;
  1405.   }
  1406. }
  1407.  
  1408. flickrService.prototype.updateActions =
  1409. function flickrService_updateActions(aURN)
  1410. {
  1411.   var coopObj = this.faves_coop.get(aURN);
  1412.   if (coopObj.isInstanceOf(this.faves_coop.Account)) {
  1413.     coopObj.enabledAction.removeAll();
  1414.     var serviceActions = this.svcCoopObj.children.enumerate();
  1415.     while (serviceActions.hasMoreElements()) {
  1416.       var action = serviceActions.getNext();
  1417.       if (action.flavour == "accountaction" ||
  1418.           action.flavour == "view")
  1419.       {
  1420.         coopObj.enabledAction.add(action);
  1421.       }
  1422.     }
  1423.   }
  1424. }
  1425.  
  1426.  
  1427. // JMC - XXX TODO, factor common code out of these two methods
  1428. flickrService.prototype.addFoafPerson =
  1429. function flickrService_addFoafPerson(aPhotoPerson, aFriendOfURN)
  1430. {
  1431.   this._logger.info(".addFoafPerson('"+aFriendOfURN+"')");
  1432. }
  1433.  
  1434.  
  1435. flickrService.prototype.addPhotostream =
  1436. function flickrService_addPhotostream(aPhotoPerson, aCoopAccount)
  1437. {
  1438.   this._logger.info(".addPhotostream('"+aPhotoPerson.username+"')");
  1439.   var aQuery = new queryHelper();
  1440.   aQuery.user = aPhotoPerson.id;
  1441.   aQuery.username = aPhotoPerson.fullname;
  1442.   var mediaqueryURN = "urn:media:favorites:flickr:"+aQuery.stringVal;
  1443.   var mediaquery = this.faves_coop.get(mediaqueryURN);
  1444.   if (!mediaquery) {
  1445.     mediaquery = new this.faves_coop.MediaQuery(
  1446.       mediaqueryURN,
  1447.       {
  1448.         serviceId: PHOTOAPIMGR_CONTRACTID,
  1449.         service: this.shortName,
  1450.         favicon: FLICKR_FAVICON
  1451.       }
  1452.     );
  1453.   }
  1454.   mediaquery.query = aQuery.stringVal;
  1455.   mediaquery.name = aPhotoPerson.fullname;
  1456.   mediaquery.isPollable = true;
  1457.   mediaquery.isTransient = aCoopAccount.isTransient;
  1458.   var mediaFavesURN = "urn:media:favorites";
  1459.   var mediaFaves = this.faves_coop.get(mediaFavesURN);
  1460.   if (!mediaFaves) {
  1461.     mediaFaves = new this.faves_coop.Folder(mediaFavesURN);
  1462.     this.faves_coop.favorites_root.children.add(mediaFaves);
  1463.   }
  1464.   mediaFaves.children.addOnce(mediaquery);
  1465. }
  1466.  
  1467. flickrService.prototype.addCoopPerson =
  1468. function flickrService_addCoopPerson(aPhotoPerson, refreshItem)
  1469. {
  1470. }
  1471.  
  1472.  
  1473. // BEGIN flockIPollingService
  1474. flickrService.prototype.refresh =
  1475. function flickrService_refresh(aURN, aListener)
  1476. {
  1477.   this._logger.info("{flockIPollingService}.refresh('"+aURN+"', ...)");
  1478.   var refreshItem = this.faves_coop.get(aURN);
  1479.   var inst = this;
  1480.   if (refreshItem instanceof this.faves_coop.Account) {
  1481.     if (!refreshItem.isAuthenticated) {
  1482.       // aListener.onError();
  1483.       return;
  1484.     }
  1485.  
  1486.     this.getContacts({
  1487.       onGetContactsResult: function (enumPeople) {
  1488.         // Found some people
  1489.         function myWorker(shouldYield) {
  1490.            var count = 0;
  1491.            while (enumPeople.hasMoreElements()) {
  1492.              var person = enumPeople.getNext();
  1493.              count++;
  1494.              //inst.addCoopPerson(person, refreshItem);
  1495.              inst.addPhotostream(person, refreshItem);
  1496.              if (shouldYield())  {
  1497.                yield;
  1498.              }
  1499.            }
  1500.            aListener.onResult();
  1501.         }
  1502.         getCompTK().schedule(gTimers, 0.3, 30, myWorker);
  1503.       },
  1504.       onError: function (aError) {
  1505.         //aListener.onError(aError);
  1506.       }
  1507.     });
  1508.     // Essentially the importStreams actions
  1509.   }
  1510.   aListener.onResult();
  1511. }
  1512. // END flockIPollingService
  1513.  
  1514.  
  1515. flickrService.prototype.migrateAccount =
  1516. function flickrService_migrateAccount( aId, aUsername ) {
  1517.   this.init();
  1518.  
  1519.   var token = '';
  1520.   /*
  1521.   try {
  1522.     token = flock_getCharPref('flock.photo.flickr.token');
  1523.   } catch (e) { }
  1524.   */
  1525.  
  1526.   this.addAccountById( aId, false, null, aUsername, token);
  1527. }
  1528.  
  1529. // BEGIN flockIWebService interface
  1530. flickrService.prototype.addAccountById =
  1531. function flickrService_addAccountById(aAccountID, aIsTransient, aListener, aUsername, aToken)
  1532. {
  1533.   this._logger.info("{flockIWebService}.addAccountById('"+aAccountID+"', "+aIsTransient+")");
  1534.  
  1535.   if (!aUsername) {
  1536.     // Get the password associated with this account
  1537.     var pw = this.acUtils.getPassword(this.urn+':'+aAccountID);
  1538.     // FIX ME - name shouldn't be email address (pw.user) - ja
  1539.     var name = (pw) ? pw.user : aAccountID;
  1540.     var token = '';
  1541.     var pollable = false;
  1542.     var auth = false;
  1543.   } else {
  1544.     var name = aUsername;
  1545.     var token = aToken;
  1546.     var pollable = true;
  1547.     var auth = true;
  1548.   }
  1549.  
  1550.   // Account
  1551.   var accountURN = "urn:flock:flickr:account:"+aAccountID;
  1552.   var account = new this.faves_coop.Account(accountURN, {
  1553.     name: name,
  1554.     accountId: aAccountID,
  1555.     serviceId: FLICKR_CONTRACTID,
  1556.     service: this.svcCoopObj,
  1557.     favicon: FLICKR_FAVICON,
  1558.     URL: gStrings["userprofile"].replace("%accountid%", aAccountID),
  1559.     isTransient: aIsTransient,
  1560.     isPollable: pollable,
  1561.     authToken: token,
  1562.     isAuthenticated: auth
  1563.   });
  1564.   this.faves_coop.accounts_root.children.add(account);
  1565.  
  1566.   /*********************
  1567.   /* PHAN: taking these streams out for now. Matt will need them
  1568.            later for people
  1569.  
  1570.   // Photostream
  1571.   var photostreamURN = "urn:flock:stream:photo:flickr:"+aAccountID;
  1572.   var photostream = new this.faves_coop.Stream(photostreamURN, {
  1573.     name: name + "'s PhotoStream",
  1574.     favicon: FLICKR_FAVICON,
  1575.     isPollable: true,
  1576.     apiType: "flickr",
  1577.     serviceId: PHOTOAPIMGR_CONTRACTID,
  1578.     userid: aAccountID,
  1579.     flockType: "photostream",
  1580.     URL: gStrings["userphotos"].replace("%accountid%", aAccountID)
  1581.   });
  1582.   account.children.addOnce(photostream);
  1583.  
  1584.   var notificationstream = new this.faves_coop.Stream(accountURN+":notifications", {
  1585.     isPollable: false,
  1586.     isIndexable: false,
  1587.     notify: true,
  1588.     serviceId: FLICKR_CONTRACTID
  1589.   });
  1590.   account.children.addOnce(notificationstream);
  1591.  
  1592.   // Flickr comments activity
  1593.   var commentstream = new this.faves_coop.Feed(accountURN+":comments", {
  1594.     name: "Flickr Comments",
  1595.     URL: gStrings["commentsreceivedRSS"].replace("%accountid%", aAccountID),
  1596.     favicon: FLICKR_FAVICON,
  1597.     serviceId: "@flock.com/feed-manager;1"
  1598.   });
  1599.   account.children.addOnce(commentstream);
  1600.  
  1601.   var replystream = new this.faves_coop.Feed(accountURN+":replies", {
  1602.     name: "Flickr Replies",
  1603.     URL: gStrings["commentrepliesRSS"].replace("%accountid%", aAccountID),
  1604.     favicon: FLICKR_FAVICON,
  1605.     serviceId: "@flock.com/feed-manager;1"
  1606.   });
  1607.   account.children.addOnce(replystream);
  1608.  
  1609.   ****************/
  1610.  
  1611.   this.updateActions(accountURN);
  1612.  
  1613.   // Instanciate account component
  1614.   var acct = this.getAccount(account.id());
  1615.   if (aListener) aListener.onSuccess(acct, "addAccount");
  1616.   return acct;
  1617. }
  1618. // END flockIWebService interface
  1619.  
  1620.  
  1621. // BEGIN flockIAuthenticateNewAccount interface
  1622. flickrService.prototype.authenticateNewAccount =
  1623. function flickrService_authenticateNewAccount(aListener)
  1624. {
  1625.   this._logger.info("{flockIAuthenticateNewAccount}.authenticateNewAccount(aListener)");
  1626.   aListener.onStart(null, "newaccountstarted");
  1627.   var inst = this;
  1628.   var frobListener = {
  1629.     onResult: function (aXML) {
  1630.       inst._logger.info(".authenticateNewAccount(): frobListener: onResult()");
  1631.       var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1632.                          .getService(Components.interfaces.nsIWindowMediator);
  1633.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  1634.                          .getService(Components.interfaces.nsIWindowWatcher);
  1635.       var topWin = wm.getMostRecentWindow(null);
  1636.       var frob = aXML.getElementsByTagName("frob")[0].firstChild.nodeValue;
  1637.       var authURL = inst.api.getAuthUrl(frob, "write");
  1638.       var url = "chrome://browser/content/flock/photo/photoLoginWindow.xul?"
  1639.               + "url="+escape(authURL)+"&finalString=logout.gne";
  1640.       var chrome = "chrome,close,titlebar,resizable=yes,toolbar,dialog=no,"
  1641.                  + "scrollbars=yes,modal,centerscreen";
  1642.       topWin.open(url, "_blank", chrome);
  1643.  
  1644.  
  1645.       var acctURN = inst.acUtils.getFirstAuthenticatedAccountForService(FLICKR_CONTRACTID);
  1646.       var account = null;
  1647.       try {
  1648.         account = inst.getAccount(acctURN);
  1649.         if (!account) throw "ACCOUNT WAS NOT CREATED";
  1650.       } catch (ex) {
  1651.         inst._logger.info(".authenticateNewAccount(): frobListener: onResult(): ERROR: account was not created");
  1652.         if (aListener) aListener.onError(ex);
  1653.       }
  1654.       if (aListener) aListener.onSuccess(account, "", null);
  1655.     },
  1656.     onError: function (aError) {
  1657.       inst._logger.info(".authenticateNewAccount(): frobListener: onError()");
  1658.       if (aListener) aListener.onError(aError);
  1659.     }
  1660.   };
  1661.   this.api.call(frobListener, "flickr.auth.getFrob", null);
  1662. }
  1663. // END flockIAuthenticateNewAccount
  1664.  
  1665.  
  1666. // BEGIN flockIManageableWebService interface
  1667. flickrService.prototype.updateAccountStatusFromDocument =
  1668. function flickrService_updateAccountStatusFromDocument(aDocument)
  1669. {
  1670.   this._logger.info("{flockIManageableWebService}.updateAccountStatusFromDocument()");
  1671.   if (this.webDetective.detect("flickr", "loggedout", aDocument, null))
  1672.   {
  1673.     this.acUtils.markAllAccountsAsLoggedOut(FLICKR_CONTRACTID);
  1674.     // Also de-authenticate the API
  1675.     this.api.logout();
  1676.     this.state = flockIPhotoAPI.LOGGED_OUT;
  1677.     flock_refreshMediabarIfHasPrivate()
  1678.   } else if (this.webDetective.detect("flickr", "loggedin", aDocument, null)) {
  1679.     var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1680.                             .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1681.     if (this.webDetective.detect("flickr", "accountinfo", aDocument, results)) {
  1682.       var accountID = results.getPropertyAsAString("accountid");
  1683.       if (accountID && accountID.length) {
  1684.         var accountURN = this.acUtils.getAccountURNById(this.urn, accountID);
  1685.         var acct = this.faves_coop.get(accountURN);
  1686.         var username = results.getPropertyAsAString("username");
  1687.         if (username && username.length) {
  1688.           acct.name = username;
  1689.         }
  1690.         var avatarURL = results.getPropertyAsAString("avatarURL");
  1691.         if (avatarURL && avatarURL.length) {
  1692.           acct.avatar = avatarURL;
  1693.         }
  1694.         if (!acct.isAuthenticated) {
  1695.           var inst = this;
  1696.           var loginListener = {
  1697.             onAuth: function () {
  1698.               inst._logger.info(".updateAccountStatusFromDocument(): loginListener: onAuth()");
  1699.             },
  1700.             onError: function (aError) {
  1701.               inst._logger.info(".updateAccountStatusFromDocument(): loginListener: onError()");
  1702.               inst._logger.info(aError ? (aError.errorString) : "No details");
  1703.               acct.isAuthenticated = false;
  1704.             }
  1705.           };
  1706.           this.login(accountURN, loginListener);
  1707.           // I'm going to somewhat prematurely assume that the authentication
  1708.           // will succeed.  If it fails, the listener will catch it.
  1709.           acct.isAuthenticated = true;
  1710.         }
  1711.       }
  1712.     }
  1713.   }
  1714. }
  1715. // END flockIManageableWebService interface
  1716.  
  1717.  
  1718. // BEGIN flockISocialWebService interface
  1719. flickrService.prototype.decorateForPerson =
  1720. function flickrService_decorateForPerson(aDocument)
  1721. {
  1722.   this._logger.info("{flockISocialWebService}.decorateForPerson()");
  1723. }
  1724.  
  1725. flickrService.prototype.browseFriends =
  1726. function flickrService_browseFriends(aFriendURN, aListener)
  1727. {
  1728.   this._logger.info("{flockISocialWebService}.browseFriends('"+aFriendURN+"')");
  1729.   var inst = this;
  1730.   if (this.foafController) {
  1731.     this.foafController.cancelMe();
  1732.   }
  1733.   if (!aFriendURN) return;
  1734.   var friend = this.faves_coop.get(aFriendURN);
  1735.   if (!friend) {
  1736.     aListener.onError(null, "notAContact");
  1737.     return;
  1738.   }
  1739.  
  1740.   var contactListener = {
  1741.     onGetContactsResult: function(enum_) {
  1742.       var lister = this;
  1743.       function myWorker(shouldYield) {
  1744.         var count = 0;
  1745.         while (enum_.hasMoreElements()) {
  1746.           if (lister.cancel) return;
  1747.           var person = enum_.getNext();
  1748.           count++;
  1749.           inst.addFoafPerson(person, aFriendURN);
  1750.           if (count % 3 == 0 && shouldYield()) yield;
  1751.         }
  1752.         aListener.onSuccess(enum_, "success");
  1753.       }
  1754.       getCompTK().schedule(gTimers, 0.4, 40, myWorker);
  1755.     },
  1756.     onError: function() {
  1757.       inst._logger.info("JMC: uhoh....");
  1758.     },
  1759.     cancelMe: function() {
  1760.       this.cancel = true;
  1761.     }
  1762.   };
  1763.   this.foafController = contactListener;
  1764.   this.getFriendsContacts(contactListener, friend.accountId);
  1765. }
  1766. // END flockISocialWebService interface
  1767.  
  1768.  
  1769. // BEGIN flockIMediaWebService interface
  1770. flickrService.prototype.decorateForMedia =
  1771. function flickrService_decorateForMedia(aDocument)
  1772. {
  1773.   this._logger.info("{flockIMediaWebService}.decorateForMedia(aDocument)");
  1774.   var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1775.                           .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1776.   var mediaArr = [];
  1777.   if (this.webDetective.detect("flickr", "person", aDocument, results)) {
  1778.     var media = {
  1779.       name: results.getPropertyAsAString("username"),
  1780.       query: "user:" + results.getPropertyAsAString("userid") + "|username:" + results.getPropertyAsAString("username"),
  1781.       label: results.getPropertyAsAString("username"),
  1782.       favicon: this.icon,
  1783.       service: this.shortName
  1784.     }
  1785.     mediaArr.push(media);
  1786.   }
  1787.   
  1788.   if (this.webDetective.detect("flickr", "pool", aDocument, results)) {
  1789.     var media = {
  1790.       name: results.getPropertyAsAString("groupname"),
  1791.       query: "pool:" + results.getPropertyAsAString("groupid") + "|albumlabel:" + results.getPropertyAsAString("groupname"),
  1792.       label: results.getPropertyAsAString("groupname"),
  1793.       favicon: this.icon,
  1794.       service: this.shortName
  1795.     }
  1796.     mediaArr.push(media);
  1797.   }
  1798.   
  1799.   if (mediaArr.length > 0) {
  1800.     if (!aDocument._flock_decorations) {
  1801.       aDocument._flock_decorations = {};
  1802.     }
  1803.     aDocument._flock_decorations.mediaArr = mediaArr;
  1804.     this.obs.notifyObservers(aDocument, "media", "media:update");
  1805.   }
  1806. }
  1807.  
  1808. flickrService.prototype.handlesMediaStream =
  1809. function flickrService_handlesMediaStream() 
  1810. {
  1811.   return true;
  1812. }
  1813.  
  1814. flickrService.prototype.checkIsStreamUrl =
  1815. function flickrService_checkIsStreamUrl(aUrl)
  1816. {
  1817.   this._logger.debug("Checking if url is flickr stream: " + aUrl);
  1818.   if (aUrl.match(/^https?:\/\/([^\/]*\.)?flickr\.com\/photos\/upload($|\/)/) ||
  1819.       aUrl.match(/^https?:\/\/([^\/]*\.)?flickr\.com\/photos\/organize($|\/)/) ||
  1820.       aUrl.match(/^https?:\/\/([^\/]*\.)?flickr\.com\/photos\/tags($|\/)/) ||
  1821.       aUrl.match(/^https?:\/\/([^\/]*\.)?flickr\.com\/photos\/search($|\/)/) ||
  1822.       aUrl.match(/^https?:\/\/[^\.]*\.?flickr\.com\/photos\/([^\/]+)\/.+/) ||
  1823.       aUrl.match(/^https?:\/\/farm\d*\.static\.flickr\.com\/.*/) ) {
  1824.     return true;
  1825.   }
  1826.   return false;
  1827. }
  1828.  
  1829. flickrService.prototype.getMediaQueryFromURL =
  1830. function flickrService_getMediaQueryFromURL(aUrl, aListener)
  1831. {
  1832.   var myListener = {
  1833.     onResult:function (aXML) {
  1834.       try {
  1835.         var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  1836.                                 .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  1837.         var userID = aXML.getElementsByTagName('user')[0].getAttribute('id');
  1838.         var userName = aXML.getElementsByTagName('username')[0].firstChild.nodeValue;
  1839.  
  1840.         results.setPropertyAsAString("query", "user:" + userID + "|username:" + userName);
  1841.         results.setPropertyAsAString("title", userName);
  1842.         aListener.onSuccess(results, "query");
  1843.       } catch (ex) {
  1844.         aListener.onError(null, "Unable to get user.", null);
  1845.       }
  1846.     },
  1847.     onError: function (aError) {
  1848.         aListener.onError(null, aError.errorString, null);
  1849.     }
  1850.   }
  1851.  
  1852.   this._logger.debug("Finding User ID from Url: " + aUrl);
  1853.   var params = {};
  1854.   params.url = aUrl
  1855.   var dict = params2Dictionary(params);
  1856.   this.call(myListener, "flickr.urls.lookupUser", dict);
  1857. }
  1858.  
  1859. // END flockIMediaWebService
  1860.  
  1861. // ========== END flickrService class ==========
  1862.  
  1863.  
  1864.  
  1865. // ================================================
  1866. // ========== BEGIN XPCOM Module support ==========
  1867. // ================================================
  1868.  
  1869. function createModule(aParams) {
  1870.   return {
  1871.     registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
  1872.       aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1873.       aCompMgr.registerFactoryLocation( aParams.CID, aParams.componentName,
  1874.                                         aParams.contractID, aFileSpec,
  1875.                                         aLocation, aType );
  1876.       var catMgr = Cc["@mozilla.org/categorymanager;1"]
  1877.         .getService(Ci.nsICategoryManager);
  1878.       if (!aParams.categories) { aParams.categories = []; }
  1879.       for (var i = 0; i < aParams.categories.length; i++) {
  1880.         var cat = aParams.categories[i];
  1881.         catMgr.addCategoryEntry( cat.category, cat.entry,
  1882.                                  cat.value, true, true );
  1883.       }
  1884.     },
  1885.     getClassObject: function (aCompMgr, aCID, aIID) {
  1886.       if (!aCID.equals(aParams.CID)) { throw Cr.NS_ERROR_NO_INTERFACE; }
  1887.       if (!aIID.equals(Ci.nsIFactory)) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
  1888.       return { // Factory
  1889.         createInstance: function (aOuter, aIID) {
  1890.           if (aOuter != null) { throw Cr.NS_ERROR_NO_AGGREGATION; }
  1891.           var comp = new aParams.componentClass();
  1892.           if (aParams.implementationFunc) { aParams.implementationFunc(comp); }
  1893.           return comp.QueryInterface(aIID);
  1894.         }
  1895.       };
  1896.     },
  1897.     canUnload: function (aCompMgr) { return true; }
  1898.   };
  1899. }
  1900.  
  1901. // NS Module entrypoint
  1902. function NSGetModule(aCompMgr, aFileSpec) {
  1903.   return createModule({
  1904.     componentClass: flickrService,
  1905.     CID: FLICKR_CID,
  1906.     contractID: FLICKR_CONTRACTID,
  1907.     componentName: CATEGORY_COMPONENT_NAME,
  1908.     implementationFunc: function (aComp) { getCompTK().addAllInterfaces(aComp); },
  1909.     categories: [
  1910.       { category: "wsm-startup", entry: CATEGORY_COMPONENT_NAME, value: FLICKR_CONTRACTID },
  1911.       { category: "flockIPhotoAPI", entry: CATEGORY_ENTRY_NAME, value: FLICKR_CONTRACTID },
  1912.       { category: "flockWebService", entry: CATEGORY_ENTRY_NAME, value: FLICKR_CONTRACTID }
  1913.     ]
  1914.   });
  1915. }
  1916.  
  1917. // ========== END XPCOM Module support ==========
  1918.  
  1919.  
  1920.  
  1921. // =============================================
  1922. // ========== BEGIN MultiGetter class ==========
  1923. // =============================================
  1924.  
  1925. function MultiGetter() {
  1926. }
  1927.  
  1928. MultiGetter.prototype = {
  1929.   mTimer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
  1930.   init: function(aSvc, aListener, aEnumerator) {
  1931.     this.mHasNew = false;
  1932.     this.mNeedSave = false;
  1933.     this.mSvc = aSvc;
  1934.     this.mListener = aListener;
  1935.     this.mArray = [];
  1936.     this.mMap = {};
  1937.     while (aEnumerator.hasMoreElements()) {
  1938.       var p = aEnumerator.getNext();
  1939.       p.QueryInterface(Components.interfaces.flockIPhotoPerson);
  1940.  
  1941.       this.mMap[p.id] = p;
  1942.       this.mArray.push(p.id);
  1943.       //this._logger.info(p + " " + p.id + " " + this.mArray.length + " initting \n");
  1944.     }
  1945.     if (this.mSvc.isLoggedIn) {
  1946.       this.doBiggee();
  1947.     }
  1948.     else {
  1949.       this.next();
  1950.     }
  1951.   },
  1952.   updatePerson: function(aPerson, aPhoto) {
  1953.     //this._logger.info(aPerson.username + "updating person\n");
  1954.     var seq = aPerson.seq;
  1955.     var newSeq = parseInt(aPhoto.id);
  1956.     if (newSeq > seq) {
  1957.       var oldHasNew = aPerson.hasNew;
  1958.       aPerson.seq = aPhoto.id;
  1959.       this.mNeedSave = true;
  1960.       if (seq != 0) {
  1961.         this.mHasNew = true;
  1962.         aPerson.hasNew = true
  1963.         if (oldHasNew == false) {
  1964.           aPerson.lastNewSeq = seq + 1;
  1965.         }
  1966.       }
  1967.     }
  1968.     this.mMap[aPerson.id] = null;// wipe this entry out
  1969.   },
  1970.   doBiggee: function() {
  1971.     var inst = this;
  1972.     var listener = {
  1973.       onSearchResult: function(aEnumerator) {
  1974.         //this._logger.info("on search result\n");
  1975.         try {
  1976.           while (aEnumerator.hasMoreElements()) {
  1977.             var photo = aEnumerator.getNext();
  1978.             var person = inst.mMap[photo.userid];  // username is actuall an id
  1979.             //this._logger.info("result\n")
  1980.             if (!person) continue;
  1981.             inst.updatePerson(person, photo);
  1982.           }
  1983.         } catch(e) {
  1984.           //this._logger.info("this" + e + " " + e.lineNumber + "\n");
  1985.         }
  1986.         inst.next();
  1987.       },
  1988.       onError: function(aError) {
  1989.         //this._logger.info("on error result\n");
  1990.         //hmm. mebbe should bail
  1991.         inst.finish();
  1992.       }
  1993.     }
  1994.     this.mSvc.getContactsPhotos(listener);
  1995.   },
  1996.   finish: function() {
  1997.     //this._logger.info("FINITI\n");
  1998.     if (this.mHasNew) {
  1999.       this.mListener.onGetMostRecentPhotoForList(Components.interfaces.flockIPhotoAPIListener.LIST_HAS_NEW);
  2000.     }
  2001.     else if (this.mNeedSave) {
  2002.       this.mListener.onGetMostRecentPhotoForList(Components.interfaces.flockIPhotoAPIListener.LIST_NEED_SAVE);
  2003.     }
  2004.     else {
  2005.       this.mListener.onGetMostRecentPhotoForList(Components.interfaces.flockIPhotoAPIListener.LIST_NO_CHANGE);
  2006.     }
  2007.     return;
  2008.   },
  2009.   notify: function() {
  2010.     this.doNext();
  2011.   },
  2012.   next: function() {
  2013.     this.mTimer.initWithCallback(this, 1000, 0);
  2014.   },
  2015.   doNext: function() {
  2016.     //this._logger.info("NEXT PLEASE\n");
  2017.     var inst = this;
  2018.     var person = null;
  2019.     for (;;) {
  2020.       if (!this.mArray.length) break;
  2021.       var id = this.mArray.pop();
  2022.       //this._logger.info(id + " " + "< NEXT PLEASE\n");
  2023.       person = this.mMap[id];
  2024.       if (person) {
  2025.         break;
  2026.       }
  2027.     }
  2028.     if (!person) {
  2029.       this.finish();
  2030.       return;
  2031.     }
  2032.  
  2033.     var listener = {
  2034.       onSearchResult: function(aEnumerator) {
  2035.         //this._logger.info("b search result\n");
  2036.         while (aEnumerator.hasMoreElements()) {
  2037.           var photo = aEnumerator.getNext();
  2038.           var person = inst.mMap[photo.userid];  // username is actuall an id
  2039.           inst.updatePerson(person, photo);
  2040.           break;
  2041.         }
  2042.         inst.next();
  2043.       },
  2044.       onError: function(aError) {
  2045.         //this._logger.info("b search eror\n");
  2046.         inst.finish();
  2047.       }
  2048.     }
  2049.     this.mSvc.search(listener, person.id, "", "", 1, 1);
  2050.   }
  2051. }
  2052.  
  2053. // ========== END MultiGetter class ==========
  2054.  
  2055.  
  2056.  
  2057. // ===========================================
  2058. // ========== BEGIN FlickrAPI class ==========
  2059. // ===========================================
  2060.  
  2061. function FlickrAPI() {
  2062.   this._logger = Components.classes['@flock.com/logger;1'].createInstance(Components.interfaces.flockILogger);
  2063.   this._logger.init('flickrAPI');
  2064.   this._logger.info('Created Flickr API Object');
  2065.  
  2066.   var inst = this;
  2067.   this.need2CreateAlbum = false;
  2068.   this.api_key = "92c2a562f0e9c2ed8dfe78f42a7734c7";
  2069.   this.api_secret = "17b26c20558cf979";
  2070.   this.endpoint = "http://www.flickr.com/services/rest/";
  2071.   this.upload_endpoint = "http://www.flickr.com/services/upload/";
  2072.   this.auth_endpoint = "http://www.flickr.com/services/auth/";
  2073.   this.req = null;
  2074.   this.frob = null;
  2075.   this.lastToken = new Date();
  2076.   this.authUser = null;
  2077.   this.filesToDelete = [];
  2078.   this.setEndpoint = function(aEndpoint) {
  2079.     this.endpoint = aEndpoint;
  2080.   };
  2081.  
  2082.   this.reset = function() {
  2083.     this.hasCreateAlbum = null;
  2084.   };
  2085.  
  2086.   this.getAuthUser = function() {
  2087.     return this.authUser;
  2088.   };
  2089.   this.getStoredToken = function(aListener) {
  2090.   };
  2091.   this.checkToken = function(aListener, aToken) {
  2092.     var tokenListener = {
  2093.       onResult: function (aXML) {
  2094.         var flickrUser = new FlickrUser();
  2095.         flickrUser.token = aXML.getElementsByTagName("token")[0].firstChild.nodeValue;
  2096.         var user = aXML.getElementsByTagName("user")[0];
  2097.         flickrUser.nsid = user.getAttribute("nsid");
  2098.         flickrUser.id = user.getAttribute("nsid");
  2099.         flickrUser.username = user.getAttribute("username");
  2100.         flickrUser.fullname = user.getAttribute("fullname");
  2101.         inst.authUser = flickrUser;
  2102.  
  2103.         inst._logger.info("token checks out");
  2104.         inst.lastToken = new Date();
  2105.         aListener.onAuth();
  2106.       },
  2107.       onError: function (aError) {
  2108.         inst._logger.info("Token does not pass muster");
  2109.         inst.frob = null;
  2110.         aListener.onError(null);
  2111.       }
  2112.     }
  2113.     var token = aToken;
  2114.     var params = {
  2115.       auth_token: token
  2116.     };
  2117.     this.call(tokenListener, "flickr.auth.checkToken", params);
  2118.   };
  2119.   this.getToken = function(aAccountURN, aListener) {
  2120.     inst._logger.info("entering getToken");
  2121.     var acctCoopObj = this.svc.faves_coop.get(aAccountURN);
  2122.     var flickrUser = new FlickrUser();
  2123.  
  2124.     // we have an existing token, let's see if it's valid
  2125.     if (acctCoopObj.authToken && acctCoopObj.authToken.length) {
  2126.       var checkTokenListener = {
  2127.         onAuth : function() {
  2128.           // the token is aiighttt lets reuse it
  2129.           inst._reuseToken(aAccountURN, aListener);
  2130.         },
  2131.  
  2132.         onError: function(aError) {
  2133.           // something
  2134.           inst._getNewToken(aAccountURN, aListener);
  2135.         }
  2136.       };
  2137.       this.checkToken(checkTokenListener, acctCoopObj.authToken);
  2138.     } else {
  2139.       this._getNewToken(aAccountURN, aListener);
  2140.     }
  2141.   };
  2142.   this._reuseToken = function(aAccountURN, aListener)  {
  2143.     inst._logger.info("entering _reuseToken");
  2144.     var acctCoopObj = inst.svc.faves_coop.get(aAccountURN);
  2145.     var flickrUser = new FlickrUser();
  2146.  
  2147.     flickrUser.token = acctCoopObj.authToken;
  2148.     flickrUser.nsid = acctCoopObj.flickr_nsid;
  2149.     flickrUser.id = acctCoopObj.flickr_id;
  2150.     flickrUser.username = acctCoopObj.flickr_username;
  2151.     flickrUser.fullname = acctCoopObj.flickr_fullname;
  2152.     this.authUser = flickrUser;
  2153.     inst.svc.acUtils.ensureOnlyAuthenticatedAccount(aAccountURN);
  2154.  
  2155.   };
  2156.   this._getNewToken = function(aAccountURN, aListener, aFrob) {
  2157.     inst._logger.info("entering _getNewToken");
  2158.     var acctCoopObj = inst.svc.faves_coop.get(aAccountURN);
  2159.     var flickrUser = new FlickrUser();
  2160.     var tokenListener = {
  2161.         onResult: function (aXML) {
  2162.           flickrUser.token = aXML.getElementsByTagName("token")[0].firstChild.nodeValue;
  2163.           var user = aXML.getElementsByTagName("user")[0];
  2164.           flickrUser.nsid = user.getAttribute("nsid");
  2165.           flickrUser.id = user.getAttribute("nsid");
  2166.           flickrUser.username = user.getAttribute("username");
  2167.           flickrUser.fullname = user.getAttribute("fullname");
  2168.           inst.authUser = flickrUser;
  2169.  
  2170.           acctCoopObj.authToken = flickrUser.token;
  2171.           acctCoopObj.flickr_nsid = flickrUser.nsid;
  2172.           acctCoopObj.flickr_id = flickrUser.id;
  2173.           acctCoopObj.flickr_username = flickrUser.username;
  2174.           acctCoopObj.flickr_fullname = flickrUser.fullname;
  2175.           inst.svc.acUtils.ensureOnlyAuthenticatedAccount(aAccountURN);
  2176.  
  2177.           inst._logger.info("got token");
  2178.           this.lastToken = new Date();
  2179.           aListener.onAuth();
  2180.         },
  2181.         onError: function (aError) {
  2182.           inst._logger.info("error getting token");
  2183.           this.frob = null;
  2184.           if (!aError.errorCode) aError.errorCode = 1002;
  2185.           aListener.onError(aError);
  2186.         }
  2187.       }
  2188.  
  2189.       var frob = aFrob;
  2190.       if (!frob) {
  2191.         frob = this.frob;
  2192.       }
  2193.       var params = {
  2194.         frob: frob
  2195.       };
  2196.       this.call(tokenListener, "flickr.auth.getToken", params);
  2197.   },
  2198.   this.isLoggedIn = function() {
  2199.     if (this.authUser) return true;
  2200.     return false;
  2201.   };
  2202.   this.logout = function() {
  2203.     this.authUser = null;
  2204.     this.frob = null;
  2205.     this.token = null;
  2206.     flock_refreshMediabarIfHasPrivate()
  2207.   };
  2208.   this.login = function(aAccountURN, aListener) {
  2209.     inst._logger.info("api.login('"+aAccountURN+"')");
  2210.     this.authUser = null;
  2211.     this.frob = null;
  2212.     var api = this;
  2213.  
  2214.     // for http://bugzilla.flock.com/show_bug.cgi?id=6255 --
  2215.     // ensure that the frob and auth token synch up
  2216.     if (0 /*acctCoopObj.authToken && acctCoopObj.authToken.length*/) {
  2217.       inst._logger.info("api.login('"+aAccountURN+"'): trying to reuse token");
  2218.       var reuseTokenListener = {
  2219.         onResult: function (aXML) {
  2220.           inst._logger.info("api.login('"+aAccountURN+"') reuseTokenListener: onResult()");
  2221.           aListener.onAuth();
  2222.         },
  2223.         onError: function (aError) {
  2224.           inst._logger.info("api.login('"+aAccountURN+"') reuseTokenListener: onError()");
  2225.           aListener.onError(aError);
  2226.         }
  2227.       };
  2228.  
  2229.      this.getToken(aAccountURN, reuseTokenListener);
  2230.     } else {
  2231.      // get new frob
  2232.      inst._logger.info("api.login('"+aAccountURN+"'): need new token");
  2233.       var frobListener = {
  2234.         onResult: function (aXML) {
  2235.           try {
  2236.             var frobNode = aXML.getElementsByTagName("frob")[0];
  2237.             var frob = frobNode.firstChild.nodeValue;
  2238.             var authUrl = api.getAuthUrl(frob, "write");
  2239.             inst.frob = frob;
  2240.             api.frob = frob;
  2241.  
  2242.             var hr = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1']
  2243.                                .createInstance(Components.interfaces.nsIXMLHttpRequest);
  2244.  
  2245.             var onReadyStateFunc = function (eEvt) {
  2246.               if (hr.readyState == 4) {
  2247.                 if (api.svc.webDetective.detectNoDOM("flickr", "apiAuth2", "", hr.responseText, null)) {
  2248.                   inst._logger.info("Successfully authorized2!");
  2249.                   api._getNewToken(aAccountURN, aListener, frob);
  2250.                 } else {
  2251.                   var results = Components.classes["@mozilla.org/hash-property-bag;1"]
  2252.                               .createInstance(Components.interfaces.nsIWritablePropertyBag2);
  2253.                   //dump("CDC: apiAuth1: "+hr.responseText+"\n");
  2254.                   if (api.svc.webDetective.detectNoDOM("flickr", "apiAuth1", "", hr.responseText, results)) {
  2255.  
  2256.                     var hr2 = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  2257.                                         .createInstance(Components.interfaces.nsIXMLHttpRequest);
  2258.                     var postBody = "magic_cookie=" + results.getPropertyAsAString("magic_cookie")
  2259.                                 + "&api_key=" + api.api_key
  2260.                                 + "&api_sig=" + results.getPropertyAsAString("api_sig")
  2261.                                 + "&perms=write"
  2262.                                 + "&frob=" + frob
  2263.                                 + "&done_auth=1";
  2264.                     hr2.onreadystatechange = function (eEvt2) {
  2265.                       if (hr2.readyState == 4) {
  2266.                         if (api.svc.webDetective.detectNoDOM("flickr", "apiAuth2", "", hr2.responseText, null)) {
  2267.                           inst._logger.info("Successfully authorized!");
  2268.                           api._getNewToken(aAccountURN, aListener, frob);
  2269.                         } else {
  2270.                           inst._logger.info("NOT successfully authorized!");
  2271.                           inst._logger.info(hr2.responseText);
  2272.                           aListener.onError(null);
  2273.                         }
  2274.                       }
  2275.                     }
  2276.                     hr2.detachLoadGroup = true;
  2277.                     hr2.overrideMimeType("text/txt");
  2278.                     hr2.open("POST", "http://www.flickr.com/services/auth/");
  2279.                     hr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  2280.                     hr2.send(postBody);
  2281.  
  2282.                   } else if (api.svc.webDetective.detectNoDOM("flickr", "apiAuth3", "", hr.responseText, null)) {
  2283.                     inst._logger.info("Successfully authorized with apiAuth3!");
  2284.                     api._getNewToken(aAccountURN, aListener, frob);
  2285.                   } else {
  2286.                     inst._logger.debug("Error - did not detect apiAuth1 or apiAuth3");
  2287.                     aListener.onError(null);
  2288.                   }
  2289.                 }
  2290.               }
  2291.             };
  2292.  
  2293.             hr.onreadystatechange = onReadyStateFunc;
  2294.             hr.detachLoadGroup = true;
  2295.             hr.overrideMimeType("text/txt");
  2296.             hr.open("GET", authUrl,true);
  2297.             hr.send(null);
  2298.             //var topWindow = windowManagerInterface.getMostRecentWindow(null);
  2299.             //var url = "chrome://browser/content/flock/photo/photoLoginWindow.xul?"+"url="+escape(authUrl)+"&finalString=logout.gne";
  2300.             //topWindow.open(url,"_blank", "chrome,close,titlebar,resizable=yes,toolbar,dialog=no,scrollbars=yes,modal,centerscreen");
  2301.           }
  2302.           catch(e) {
  2303.             inst._logger.info("caught this one in login: " + e);
  2304.           }
  2305.         },
  2306.         onError: function (aError) {
  2307.           inst._logger.debug("There was an error getting the frob.");
  2308.           aListener.onError(aError);
  2309.         }
  2310.       };
  2311.       this.call(frobListener, "flickr.auth.getFrob", null);
  2312.     }
  2313.   };
  2314.   this.add2Album = function(aUploadListener, aUpload, aID) {
  2315.     var inst = this;
  2316.     var listener = {
  2317.       onResult: function(aXML) {
  2318.         inst.finalizePhoto(aUploadListener, aUpload, aID);
  2319.       },
  2320.       onError: function(aError) {
  2321.         inst.finalizePhoto(aUploadListener, aUpload, aID);
  2322.       }
  2323.     }
  2324.     var params = {};
  2325.     params.photoset_id = aUpload.album;
  2326.     params.photo_id = aID;
  2327.     this.authenticatedCall(listener, "flickr.photosets.addPhoto", params);
  2328.   };
  2329.   /*
  2330.   this.createAlbum = function(aUploadListener, aUpload, aID) {
  2331.     var inst = this;
  2332.     var listener = {
  2333.       onCreateAlbumResult: function(aAlbum) {
  2334.       },
  2335.       onError: function(aError) {
  2336.       },
  2337.     }
  2338.     var params = {};
  2339.     params.photoset_id = aUpload.album;
  2340.     params.photo_id = aID;
  2341.     this.svc.reallyCreateAlbum(listener, call(listener, "flickr.photosets.addPhoto", params));
  2342.   };
  2343.   */
  2344.   this.finalizePhoto = function(aUploadListener, aUpload, aID) {
  2345.     try {
  2346.       var inst = this;
  2347.       var getPhotoListener = {
  2348.         onGetPhotoResult: function(aPhoto) {
  2349.           aUploadListener.onUploadFinalized(aUpload, aPhoto);
  2350.         },
  2351.         onError: function(aError) {
  2352.           aUploadListener.onError(null);
  2353.         }
  2354.       }
  2355.       inst.svc.getPhoto(getPhotoListener, aID);
  2356.     } catch(e) {
  2357.       inst._logger.info(e);
  2358.     }
  2359.   };
  2360.   this.mAddingAlbums = false;
  2361.   this.mPhotos2Album = [];
  2362.   this.processPendingAlbumAdditions = function() {
  2363.     if (this.mAddingAlbums) return;
  2364.     if (this.mPhotos2Album.length == 0) return;
  2365.     this.mAddingAlbums = true;
  2366.  
  2367.     var obj = this.mPhotos2Album[0];
  2368.     var photoid = obj.photoid;
  2369.     var albumid = obj.albumid;
  2370.     var inst = this;
  2371.  
  2372.     if (this.svc.fakeAlbums[albumid] && !this.svc.realishAlbums[albumid]) {
  2373.       var listener = {
  2374.         onCreateAlbumResult: function(aAlbum) {
  2375.           inst.svc.realishAlbums[albumid] = aAlbum.id;
  2376.           inst.mAddingAlbums = false;
  2377.         },
  2378.         onError: function(aError) {
  2379.           inst.mAddingAlbums = false;
  2380.         }
  2381.       }
  2382. //      var params = {};
  2383. //      params.photoset_id = photoid;
  2384. //      params.photo_id = albumid;
  2385.       var fakeAlbum = this.svc.fakeAlbums[albumid];
  2386.       this.svc.reallyCreateAlbum(listener,  fakeAlbum.title, photoid);
  2387.     }
  2388.     else {
  2389.       if (inst.svc.realishAlbums[albumid]) {
  2390.         albumid = inst.svc.realishAlbums[albumid];
  2391.       }
  2392.       var listener = {
  2393.         onResult: function(aXML) {
  2394.           inst.finalizePhoto(obj.listener, obj.upload, photoid);
  2395.           inst.mPhotos2Album.shift();
  2396.           inst.mAddingAlbums = false;
  2397.           inst.processPendingAlbumAdditions();
  2398.           //re-enter to optimize
  2399.         },
  2400.         onError: function(aError) {
  2401.           inst.finalizePhoto(obj.listener, obj.upload, photoid);
  2402.           inst.mPhotos2Album.shift();
  2403.           inst.mAddingAlbums = false;
  2404.           //re-enter to optimize?? this is an error condition, por
  2405.           //supuesto
  2406.         }
  2407.       }
  2408.       var params = {};
  2409.       params.photoset_id = albumid;
  2410.       params.photo_id = photoid;
  2411.       this.authenticatedCall(listener, "flickr.photosets.addPhoto", params);
  2412.     }
  2413.   };
  2414.   this.mCheckTicketsInProcess = false;
  2415.   this.processPendingUploadTickets = function() {
  2416.     if (this.mCheckTicketsInProcess) return;
  2417.  
  2418.     var ticketList = "";
  2419.  
  2420.     for (var p in this.mPendingTickets) {
  2421.       var obj = this.mPendingTickets[p];
  2422.       if (!obj) continue;//really should be removeing these - make it an array?
  2423.       ticketList += obj.ticketid + ",";
  2424.     }
  2425.     if (ticketList.length == 0) return;
  2426.  
  2427.     this.mCheckTicketsInProcess = true;
  2428.  
  2429.     var params = {
  2430.       tickets: ticketList
  2431.     };
  2432.  
  2433.     var inst = this;
  2434.  
  2435.     var ticketListener = {
  2436.       onResult: function (aXML) {
  2437.         inst.mCheckTicketsInProcess = false;
  2438.         var ticketList = aXML.getElementsByTagName("ticket");
  2439.         for (var i = 0; i < ticketList.length; i++) {
  2440.           var ticket = ticketList[i];
  2441.           var id = ticket.getAttribute("id");
  2442.           var photoid = ticket.getAttribute("photoid");
  2443.           var complete = ticket.getAttribute("complete");
  2444.           var invalid = ticket.getAttribute("invalid");
  2445.           var obj = inst.mPendingTickets[id];
  2446.  
  2447.           if (complete =="0") {
  2448.             continue;
  2449.           }
  2450.           else if (invalid) {
  2451.             obj.listener.onError(null);
  2452.           }
  2453.           else if (complete =="1") {
  2454.             if (obj.upload.album && obj.upload.album.length>0) {
  2455.               obj.photoid = photoid;
  2456.               obj.albumid = obj.upload.album;
  2457.               obj.listener = obj.listener;
  2458.               inst.mPhotos2Album.push(obj);
  2459.             }
  2460.             else {
  2461.               inst.finalizePhoto(obj.listener, obj.upload, photoid);
  2462.             }
  2463.           }
  2464.           else if (complete =="2") {
  2465.             obj.listener.onError(null);
  2466.           }
  2467.           inst.mPendingTickets[id] = null;//that doesn't really remove it tho
  2468.         }
  2469.       },
  2470.       onError: function (aXML) {
  2471.         inst.mCheckTicketsInProcess = false;
  2472.       }
  2473.     }
  2474.  
  2475.     this.call(ticketListener, "flickr.photos.upload.checkTickets", params);
  2476.   };
  2477.   this.mPendingTickets = {};
  2478.   this.upload = function(aListener, aPhoto, aParams, aUpload) {
  2479.     var inst = this;
  2480.     this.uploader = new PhotoUploader();
  2481.     var myListener = {
  2482.       onResult: function(aXML) {
  2483.         var rsp = aXML.getElementsByTagName("rsp")[0];
  2484.         var stat = rsp.getAttribute("stat");
  2485.         if (stat !="ok") {
  2486.           var err = aXML.getElementsByTagName("err")[0];
  2487.           var error= inst.getError('SERVICE_ERROR', aXML, null);
  2488.           aListener.onError(error);
  2489.         }
  2490.         else {
  2491.           aListener.onUploadComplete(aUpload);
  2492.           var ticketid = aXML.getElementsByTagName("ticketid")[0].firstChild.nodeValue;
  2493.           inst._logger.info(ticketid + "pre ticketid");
  2494.           inst.mPendingTickets[ticketid] = {
  2495.             ticketid: ticketid,
  2496.             upload: aUpload,
  2497.             listener: aListener
  2498.           }
  2499.           inst._logger.info(ticketid + "post ticketid");
  2500.           /*
  2501.           if (aUpload.album && aUpload.album.length>0) {
  2502.             var ticketid = aXML.getElementsByTagName("ticketid")[0].firstChild.nodeValue;
  2503.             inst.mPendingTickets[ticketid] = {
  2504.               ticketid: ticketid,
  2505.               upload: aUpload,
  2506.               listener: aListener,
  2507.             }
  2508.           }
  2509.           else {
  2510.             aListener.onUploadFinalized(aUpload, null);
  2511.           }
  2512.           */
  2513.         }
  2514.       },
  2515.       onError: function(aErrorCode) {
  2516.         if (aErrorCode) {
  2517.           aListener.onError(inst.getError('HTTP_ERROR', null, aErrorCode));
  2518.         } else {
  2519.           aListener.onError(inst.getError('SERVICE_ERROR', null));
  2520.         }
  2521.       },
  2522.       onProgress: function(aCurrentProgress) {
  2523.         aListener.onProgress(aCurrentProgress);
  2524.       }
  2525.     }
  2526.     this.convertBools(aParams);
  2527.     this.convertTags(aParams);
  2528.  
  2529.     aParams.auth_token = this.authUser.token;
  2530.     aParams.api_key = this.api_key;
  2531.     aParams = this.appendSignature(aParams);
  2532.  
  2533.     this.uploader.setEndpoint(this.upload_endpoint);
  2534.     this.uploader.upload(myListener, aPhoto, aParams);
  2535.     return;
  2536.   };
  2537.   this.convertBools = function(aParams) {
  2538.     for (var p in aParams) {
  2539.       if (!p.match(/^is/)) continue;
  2540.       // I hope that this doesn't break anything
  2541.       if (aParams[p]=="true") aParams[p] = "1";
  2542.       if (aParams[p]=="false") aParams[p] = "0";
  2543.     }
  2544.   };
  2545.   this.convertTags = function(aParams) {
  2546.     for (var p in aParams) {
  2547.       if (p != "tags") continue;
  2548.       var tags = aParams[p].split(",");
  2549.       for (var i = 0; i < tags.length;++i) {
  2550.         tags[i] = '"' + tags[i] + '"';
  2551.         tags[i] = tags[i].replace(/\"+/g,'"');
  2552.       }
  2553.       aParams[p] = tags.join(",");
  2554.     }
  2555.   };
  2556.   this.authenticatedCall = function(aListener, aMethod, aParams) {
  2557.     if (!aParams) aParams = {};
  2558.     aParams.auth_token = this.authUser.token;
  2559.     this.call(aListener, aMethod, aParams);
  2560.     var paramString = this.getParamString(aParams, true);
  2561.   };
  2562.   this.call = function(aListener, aMethod, aParams) {
  2563.     this.convertBools(aParams);
  2564.     this.convertTags(aParams);
  2565.     if (aParams == null) aParams = [];
  2566.     if (aMethod) aParams.method = aMethod;
  2567.     aParams.api_key = this.api_key;
  2568.     var paramString = this.getParamString(aParams, (aMethod == null));
  2569.     var url = this.endpoint + "?" + paramString;
  2570.     this._doCall(aListener, url, null);
  2571.   };
  2572.   this._doCall = function(aListener, aUrl, aContent) {
  2573.     inst._logger.info("Sending " + aUrl);
  2574.     this.req = Components.classes['@mozilla.org/xmlextras/xmlhttprequest;1']
  2575.                          .createInstance(Components.interfaces.nsIXMLHttpRequest);
  2576.     this.req.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
  2577.     this.req.open('GET', aUrl, true);
  2578.     var req = this.req;
  2579.     this.req.onreadystatechange = function (aEvt) {
  2580.       if (req.readyState == 4) {
  2581.         try {
  2582.           if (req.status/100 == 2) {
  2583.              try {
  2584.               //inst._logger.info(req.responseText);
  2585.               var rsp = req.responseXML.getElementsByTagName("rsp")[0];
  2586.               var stat = rsp.getAttribute("stat");
  2587.               if (stat !="ok") {
  2588.                 var err = req.responseXML.getElementsByTagName("err")[0];
  2589.                 var error= inst.getError('SERVICE_ERROR', req.responseXML, null);
  2590.  
  2591.                 aListener.onError(error);
  2592.               }
  2593.               else {
  2594.                 aListener.onResult(req.responseXML);
  2595.               }
  2596.             } catch (ex) {
  2597.               // error parsing xml
  2598.               inst._logger.info(ex);
  2599.               aListener.onError(inst.getError('SERVICE_ERROR', null, null));
  2600.             }
  2601.           }
  2602.           else {
  2603.             //  http errors
  2604.             aListener.onError(inst.getError("HTTP_ERROR", null, req.status));
  2605.           }
  2606.         } catch(e) {
  2607.           // XMHTTPERROR (connection lost)
  2608.           inst._logger.info(e);
  2609.           aListener.onError(inst.getError('HTTP_ERROR',null, "9001"));
  2610.         }
  2611.       }
  2612.     }
  2613.     this.req.send(null);
  2614.   };
  2615.   this.getError = function(aErrorType, aXML, aHTTPErrorCode) {
  2616.     var error = Components.classes[FLOCK_ERROR_CONTRACTID].createInstance(flockIError);
  2617.     if  (aErrorType == "HTTP_ERROR") {
  2618.       error.errorCode = aHTTPErrorCode;
  2619.     } else if (aErrorType == "SERVICE_ERROR") {
  2620.       var errorCode;
  2621.       var errorMessage;
  2622.       var serviceErrorMessage;
  2623.       try {
  2624.         errorCode = aXML.getElementsByTagName("err")[0].getAttribute('code');
  2625.         serviceErrorMessage = aXML.getElementsByTagName("err")[0].getAttribute('msg');
  2626.       } catch (ex) {
  2627.         errorCode = "999" // in case the error xml is invalid
  2628.       }
  2629.  
  2630.       switch (errorCode) {
  2631.         case "1":
  2632.           error.errorCode = error.PHOTOSERVICE_INVALID_USER;
  2633.         break;
  2634.  
  2635.         case "3":
  2636.           error.errorCode = error.PHOTOSERVICE_UPLOAD_ERROR;
  2637.         break;
  2638.  
  2639.         case "4":
  2640.           error.errorCode = error.PHOTOSERVICE_FILE_IS_OVER_SIZE_LIMIT;
  2641.         break;
  2642.  
  2643.         case "5":
  2644.           error.errorCode = error.PHOTOSERVICE_INVALID_UPLOAD_FILE;
  2645.         break;
  2646.  
  2647.         case "6":
  2648.           error.errorCode = error.PHOTOSERVICE_BANDWIDTH_REACHED;
  2649.         break;
  2650.  
  2651.         case "10":
  2652.  
  2653.         break;
  2654.  
  2655.         case "98":
  2656.           error.errorCode = error.PHOTOSERVICE_LOGIN_FAILED;
  2657.         break;
  2658.  
  2659.         case "99":
  2660.           error.errorCode = error.PHOTOSERVICE_USER_NOT_LOGGED_IN;
  2661.         break;
  2662.  
  2663.         case "100":
  2664.           error.errorCode = error.PHOTOSERVICE_INVALID_API_KEY;
  2665.           break;
  2666.  
  2667.         case "105":
  2668.           error.errorCode = error.PHOTOSERVICE_UNAVAILABLE;
  2669.         break;
  2670.  
  2671.         case "999":
  2672.           error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
  2673.         break;
  2674.  
  2675.         default:
  2676.           error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
  2677.           error.serviceErrorString = serviceErrorMessage;
  2678.         break;
  2679.       }
  2680.     }
  2681.     //inst._logger.info('<<<<<<<<<<<<<<'  + error.errorString + '\n');
  2682.     return error;
  2683.   };
  2684.   this.getAuthUrl = function(aFrob, aPerms) {
  2685.     var api_key = this.api_key;
  2686.     var params = {
  2687.       api_key: api_key,
  2688.       perms: aPerms,
  2689.       frob: aFrob
  2690.     }
  2691.  
  2692.     var paramString = this.getParamString(params, true);
  2693.     return this.auth_endpoint + "?" + paramString;
  2694.   };
  2695.   this.getParamString = function(aParams, aNoMethod) {
  2696.     aParams = this.appendSignature(aParams);
  2697.     var rval = "";
  2698.     if (!aNoMethod) rval += "method=" + aParams.method + "&";
  2699.  
  2700.     var count = 0;
  2701.     for (var p in aParams) {
  2702.       if (p.match(/method/)) continue;
  2703.  
  2704.       if (count++ != 0) rval += "&";
  2705.       rval += encodeURIComponent(p) + "=" + encodeURIComponent(aParams[p]);
  2706.     }
  2707.     return rval;
  2708.   };
  2709.   this.appendSignature = function(aParams) {
  2710.     var keys = [];
  2711.     for (var p in aParams) {
  2712.       keys.push(p);
  2713.     }
  2714.     keys.sort();
  2715.     var preHash = this.api_secret;
  2716.     for (var i = 0; i < keys.length; ++i) {
  2717.       preHash += keys[i] + aParams[keys[i]];
  2718.     }
  2719.     inst._logger.info("preHash " + preHash);
  2720.  
  2721.     var converter =
  2722.       Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  2723.                 .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  2724.     converter.charset = "UTF-8";
  2725.  
  2726.     var inputStream = converter.convertToInputStream(preHash);
  2727.     aParams.api_sig = hex_md5_stream(inputStream);
  2728.     return aParams;
  2729.   };
  2730. }
  2731.  
  2732. FlickrAPI.prototype = {};
  2733.  
  2734. // ========== END FlickrAPI class ==========
  2735.  
  2736.  
  2737.  
  2738. // ============================================
  2739. // ========== BEGIN FlickrUser class ==========
  2740. // ============================================
  2741.  
  2742. function FlickrUser() {
  2743.   this.token = "";
  2744.   this.perms = "";
  2745.   this.nsid = "";
  2746.   this.id = "";
  2747.   this.username = "";
  2748.   this.fullname = "";
  2749. }
  2750.  
  2751. FlickrUser.prototype = {};
  2752.  
  2753. // ========== END FlickrUser class =========
  2754.  
  2755.  
  2756.  
  2757. // ===============================================
  2758. // ========== BEGIN flickrAccount class ==========
  2759. // ===============================================
  2760.  
  2761. function flickrAccount()
  2762. {
  2763.   this._logger = Components.classes['@flock.com/logger;1'].createInstance(Components.interfaces.flockILogger);
  2764.   this._logger.init('flickrAccount');
  2765.   this._logger.info('Created Flickr Account Object');
  2766.  
  2767.   this.acUtils = Components.classes["@flock.com/account-utils;1"]
  2768.                            .getService(Components.interfaces.flockIAccountUtils);
  2769.   this.photoAPI = Components.classes[FLICKR_CONTRACTID]
  2770.                             .getService(Components.interfaces.flockIPhotoAPI)
  2771.                             .QueryInterface(Components.interfaces.flockIWebService);
  2772.   this._coop = Components.classes["@flock.com/singleton;1"]
  2773.                          .getService(Components.interfaces.flockISingleton)
  2774.                          .getSingleton("chrome://browser/content/flock/common/load-faves-coop.js")
  2775.                          .wrappedJSObject;
  2776.   this._ctk = {
  2777.     interfaces: [
  2778.       "nsISupports",
  2779.       "flockIWebServiceAccount",
  2780.       "flockISocialWebServiceAccount",
  2781.       "flockIMediaWebServiceAccount",
  2782.       "flockIMediaUploadAccount",
  2783.     ]
  2784.   };
  2785.   getCompTK().addAllInterfaces(this);
  2786. }
  2787.  
  2788.  
  2789. // BEGIN flockIWebServiceAccount interface
  2790. flickrAccount.prototype.urn = null;
  2791.  
  2792. flickrAccount.prototype.login =
  2793. function flickrAccount_login(aListener)
  2794. {
  2795.   this._logger.info("{flockIWebServiceAccount}.login()");
  2796.   this.photoAPI.login(this.urn, aListener);
  2797. }
  2798.  
  2799. flickrAccount.prototype.logout =
  2800. function flickrAccount_logout(aListener)
  2801. {
  2802.   this._logger.info("{flockIWebServiceAccount}.logout()");
  2803.   var acctCoopObj = this._coop.get(this.urn);
  2804.   if (acctCoopObj.isAuthenticated) {
  2805.     acctCoopObj.isAuthenticated = false;
  2806.     this.photoAPI.logout();
  2807.   }
  2808. }
  2809.  
  2810. flickrAccount.prototype.activate =
  2811. function flickrAccount_activate(aListener)
  2812. {
  2813.   this._logger.info("{flockIWebServiceAccount}.activate()");
  2814.   var acctCoopObj = this._coop.get(this.urn);
  2815.   var inst = this;
  2816.   var listener = {
  2817.     onAuth: function () {
  2818.       if (aListener) aListener.onSuccess(inst, "accountAuthorized");
  2819.       acctCoopObj.isPollable = true;
  2820.       //acctCoopObj.isAuthenticated = true;
  2821.       inst.acUtils.ensureOnlyAuthenticatedAccount(inst.urn);
  2822.       //inst.acUtils.makeTempPasswordPermanent(inst.photoAPI.urn+':'+acctCoopObj.accountId);
  2823.     },
  2824.     onError: function (aError) {
  2825.       inst._logger.info( "Flickr account activation ERROR: " +
  2826.                          (aError ? aError.errorString : "No details. :("));
  2827.       acctCoopObj.isAuthenticated = false;
  2828.     }
  2829.   };
  2830.   this.photoAPI.login(this.urn, listener);
  2831.   // I'm going to jump the gun a bit here and assume that the authentication is
  2832.   // going to succeed.  If it fails, it will be caught by the listener.  But at
  2833.   // least this will prevent any further attempts to authenticate until this
  2834.   // attempt completes.
  2835.   acctCoopObj.isAuthenticated = true;
  2836. }
  2837. // END flockIWebServiceAccount interface
  2838.  
  2839.  
  2840. // BEGIN flockISocialWebServiceAccount interface
  2841. flickrAccount.prototype.browseFriends =
  2842. function flickrAccount_browseFriends(aFriendURN, aListener)
  2843. {
  2844.   this._logger.info("{flockISocialWebServiceAccount}.browseFriends('"+aFriendURN+"')");
  2845.   this.service.browseFriends(aFriendURN, aListener);
  2846. }
  2847. // END flockISocialWebServiceAccount interface
  2848.  
  2849. // ========== END flickrAccount class ==========
  2850.